snmpit_lib.pm 37.3 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-2010 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5
6
7
# All rights reserved.
#

Mac Newbold's avatar
Mac Newbold committed
8
#
9
# Module of subroutines useful to snmpit and its modules
Mac Newbold's avatar
Mac Newbold committed
10
11
12
13
14
15
#

package snmpit_lib;

use Exporter;
@ISA = ("Exporter");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
16
@EXPORT = qw( macport portnum portiface Dev vlanmemb vlanid
17
		getTestSwitches getControlSwitches getSwitchesInStack
18
                getSwitchesInStacks getVlanIfaces
19
		getVlanPorts convertPortsFromIfaces convertPortFromIface
20
		getExperimentTrunks setVlanTag setVlanStack
21
		getExperimentVlans getDeviceNames getDeviceType
22
		getInterfaceSettings mapPortsToDevices getSwitchPrimaryStack
23
		getSwitchStacks getStacksForSwitches
24
25
		getStackType getStackLeader
		getDeviceOptions getTrunks getTrunksFromSwitches
26
                getTrunkHash 
27
		getExperimentPorts snmpitGet snmpitGetWarn snmpitGetFatal
28
                getExperimentControlPorts
29
30
                getPlannedStacksForVlans getActualStacksForVlans
                filterPlannedVlans
31
		snmpitSet snmpitSetWarn snmpitSetFatal 
32
                snmpitBulkwalk snmpitBulkwalkWarn snmpitBulkwalkFatal
33
	        setPortEnabled setPortTagged
Leigh B. Stoller's avatar
Leigh B. Stoller committed
34
		printVars tbsort getExperimentCurrentTrunks
35
	        getExperimentVlanPorts
36
                uniq isSwitchPort getPathVlanIfaces);
Mac Newbold's avatar
Mac Newbold committed
37
38

use English;
39
use libdb;
40
use libtestbed;
41
use libtblog qw(tbdie tbwarn tbreport SEV_ERROR);
42
43
use Experiment;
use Lan;
44
use strict;
45
46
47
use SNMP;

my $TBOPS = libtestbed::TB_OPSEMAIL;
Mac Newbold's avatar
Mac Newbold committed
48
49
50

my $debug = 0;

51
my $DEFAULT_RETRIES = 10;
52

53
54
my $SNMPIT_GET = 0;
my $SNMPIT_SET = 1;
55
my $SNMPIT_BULKWALK = 2;
56

Mac Newbold's avatar
Mac Newbold committed
57
my %Devices=();
58
# Devices maps device names to device IPs
Mac Newbold's avatar
Mac Newbold committed
59
60

my %Interfaces=();
61
# Interfaces maps pcX:Y<==>MAC
Mac Newbold's avatar
Mac Newbold committed
62

Leigh B. Stoller's avatar
Leigh B. Stoller committed
63
64
65
my %PortIface=();
# Maps pcX:Y<==>pcX:iface

Mac Newbold's avatar
Mac Newbold committed
66
my %Ports=();
67
68
69
70
71
72
73
# Ports maps pcX:Y<==>switch:port

my %vlanmembers=();
# vlanmembers maps id -> members

my %vlanids=();
# vlanids maps pid:eid <==> id
Mac Newbold's avatar
Mac Newbold committed
74

75
76
my $snmpitErrorString;

77
78
79
# Protos
sub getTrunkPath($$$$);

80
#
81
# Initialize the library
82
83
84
85
86
#
sub init($) {
    $debug = shift || $debug;
    &ReadTranslationTable;
    return 0;
Mac Newbold's avatar
Mac Newbold committed
87
88
}

89
90
91
#
# Map between interfaces and mac addresses
#
Mac Newbold's avatar
Mac Newbold committed
92
sub macport {
93
94
    my $val = shift || "";
    return $Interfaces{$val};
Mac Newbold's avatar
Mac Newbold committed
95
96
}

97
#
Leigh B. Stoller's avatar
Leigh B. Stoller committed
98
99
100
101
102
103
104
105
106
# Map between node:iface and port numbers
#
sub portiface {
    my $val = shift || "";
    return $PortIface{$val};
}

#
# Map between switch interfaces and port numbers
107
#
Mac Newbold's avatar
Mac Newbold committed
108
sub portnum {
109
110
    my $val = shift || "";
    return $Ports{$val};
Mac Newbold's avatar
Mac Newbold committed
111
112
}

113
114
115
#
# Map between interfaces and the devices they are attached to
#
Mac Newbold's avatar
Mac Newbold committed
116
sub Dev {
117
118
    my $val = shift || "";
    return $Devices{$val};
Mac Newbold's avatar
Mac Newbold committed
119
120
}

121
122
123
124
125
126
127
#
# This function fills in %Interfaces and %Ports
# They hold pcX:Y<==>MAC and pcX:Y<==>switch:port respectively
#
sub ReadTranslationTable {
    my $name="";
    my $mac="";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
128
    my $iface="";
129
130
131
    my $switchport="";

    print "FILLING %Interfaces\n" if $debug;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
132
133
    my $result =
	DBQueryFatal("select node_id,card,port,mac,iface from interfaces");
134
135
    while ( @_ = $result->fetchrow_array()) {
	$name = "$_[0]:$_[1]";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
136
	$iface = "$_[0]:$_[4]";
137
138
139
140
	if ($_[2] != 1) {$name .=$_[2]; }
	$mac = "$_[3]";
	$Interfaces{$name} = $mac;
	$Interfaces{$mac} = $name;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
141
142
	$PortIface{$name} = $iface;
	$PortIface{$iface} = $name;
143
144
145
146
147
148
	print "Interfaces: $mac <==> $name\n" if $debug > 1;
    }

    print "FILLING %Ports\n" if $debug;
    $result = DBQueryFatal("select node_id1,card1,port1,node_id2,card2,port2 ".
	    "from wires;");
149
150
151
    while ( my @row = $result->fetchrow_array()) {
        my ($node_id1, $card1, $port1, $node_id2, $card2, $port2) = @row;
	$name = "$node_id1:$card1";
152
	print "Name='$name'\t" if $debug > 2;
153
154
	print "Dev='$node_id2'\t" if $debug > 2;
	$switchport = "$node_id2:$card2.$port2";
155
156
157
158
159
160
	print "switchport='$switchport'\n" if $debug > 2;
	$Ports{$name} = $switchport;
	$Ports{$switchport} = $name;
	print "Ports: '$name' <==> '$switchport'\n" if $debug > 1;
    }

161
162
}

163
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
191
192
193
194
#
# Return an array of ifaces belonging to the VLAN
#
sub getVlanIfaces($) {
    my $vlanid = shift;
    my @ports = ();

    my $vlan = VLan->Lookup($vlanid);
    if (!defined($vlan)) {
        die("*** $0:\n".
	    "    No vlanid $vlanid in the DB!\n");
    }
    my @members;
    if ($vlan->MemberList(\@members) != 0) {
        die("*** $0:\n".
	    "    Unable to load members for $vlan\n");
	}
    foreach my $member (@members) {
	my $nodeid;
	my $iface;
	
	if ($member->GetAttribute("node_id", \$nodeid) != 0 ||
	    $member->GetAttribute("iface", \$iface) != 0) {
	    die("*** $0:\n".
		"    Missing attributes for $member in $vlan\n");
	}
	push(@ports, "$nodeid:$iface");
    }

    return @ports;
}

195
196
197
198
199
#
# Get real ifaces on switch node in a VLAN that implements a path
# that consists of two layer 1 connections and also has a switch as
# the middle node.
#
200
sub getPathVlanIfaces($$) {
201
    my $vlanid = shift;
202
    my $ifaces = shift;
203
204
205
206
207

    my $vlan = VLan->Lookup($vlanid);
    my $experiment = $vlan->GetExperiment();
    my $pid = $experiment->pid();
    my $eid = $experiment->eid();
208
209
210
    
    my %ifacesonswitchnode = ();
    
211
212
    # find the underline path of the link
    my $query_result =
213
	DBQueryWarn("select distinct implemented_by_path from ".
214
215
216
217
		    "virt_lans where pid='$pid' and eid='$eid' and vname='".
		    $vlan->vname()."';");
    if (!$query_result || !$query_result->numrows) {
	warn "Can't find VLAN $vlanid definition in DB.";
218
	return -1;
219
220
221
222
    }

    # default implemented_by is empty
    my ($path) = $query_result->fetchrow_array();
223
    if (!$path || $path eq "") {
224
	print "VLAN $vlanid is not implemented by a path\n" if $debug;
225
	return -1;
226
227
228
229
230
231
232
    }

    # find the segments of the path
    $query_result = DBQueryWarn("select segmentname, segmentindex from virt_paths ".
				"where pid='$pid' and eid='$eid' and pathname='$path';");
    if (!$query_result || !$query_result->numrows) {
	warn "Can't find path $path definition in DB.";
233
	return -1;
234
235
    }

236
    if ($query_result->numrows > 2) {
237
	warn "We can't handle the path with more than two segments.";
238
	return -1;
239
240
241
242
243
244
245
246
247
    }
    
    my @vlans = ();
    VLan->ExperimentVLans($experiment, \@vlans);
    
    while (my ($segname, $segindex) = $query_result->fetchrow())
    {
	foreach my $myvlan (@vlans)
	{	    
248
	    if ($myvlan->vname eq $segname) {
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
		my @members;

		$vlan->MemberList(\@members);		
		foreach my $member (@members) {
		    my ($node,$iface);

		    $member->GetAttribute("node_id",  \$node);
		    $member->GetAttribute("iface", \$iface);

		    if ($myvlan->IsMember($node, $iface)) {
			my @pref;

			$myvlan->PortList(\@pref);

			# only two ports allowed in the vlan
			if (@pref != 2) {
			    warn "Vlan ".$myvlan->id()." doesnot have exact two ports.\n";
266
			    return -1;
267
268
			}

269
			if ($pref[0] eq "$node:$iface") {
270
271
272
273
274
275
276
277
278
279
			    $ifacesonswitchnode{"$node:$iface"} = $pref[1];
			} else {
			    $ifacesonswitchnode{"$node:$iface"} = $pref[0];
			}
		    }
		}
	    }
	}
    }

280
281
    %$ifaces = %ifacesonswitchnode;
    return 0;
282
283
}

284

285
286
287
288
289
290
291
292
293
#
# Returns an array of ports (in node:card form) used by the given VLANs
#
sub getVlanPorts (@) {
    my @vlans = @_;
    # Silently exit if they passed us no VLANs
    if (!@vlans) {
	return ();
    }
294
    my @ports = ();
295

296
    foreach my $vlanid (@vlans) {
297
298
	my @ifaces = getVlanIfaces($vlanid);
	push @ports, @ifaces;
299
    }
300
301
302
303
    # Convert from the DB format to the one used by the snmpit modules
    return convertPortsFromIfaces(@ports);
}

304
#
305
306
# Returns an an array of trunked ports (in node:card form) used by an
# experiment
307
308
309
310
311
#
sub getExperimentTrunks($$) {
    my ($pid, $eid) = @_;
    my @ports;

312
313
314
315
316
    my $query_result =
	DBQueryFatal("select distinct r.node_id,i.iface from reserved as r " .
		     "left join interfaces as i on i.node_id=r.node_id " .
		     "where r.pid='$pid' and r.eid='$eid' and " .
		     "      i.trunk!=0");
317

318
    while (my ($node, $iface) = $query_result->fetchrow()) {
319
320
321
322
323
324
	$node = $node . ":" . $iface;
	push @ports, $node;
    }
    return convertPortsFromIfaces(@ports);
}

Leigh B. Stoller's avatar
Leigh B. Stoller committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
#
# Returns an an array of trunked ports (in node:card form) used by an
# experiment. These are the ports that are actually in trunk mode,
# rather then the ports we want to be in trunk mode (above function).
#
sub getExperimentCurrentTrunks($$) {
    my ($pid, $eid) = @_;
    my @ports;

    my $query_result =
	DBQueryFatal("select distinct r.node_id,i.iface from reserved as r " .
		     "left join interface_state as i on i.node_id=r.node_id " .
		     "where r.pid='$pid' and r.eid='$eid' and " .
		     "      i.tagged!=0");

    while (my ($node, $iface) = $query_result->fetchrow()) {
	$node = $node . ":" . $iface;
	push @ports, $node;
    }
    return convertPortsFromIfaces(@ports);
}

#
# Returns an an array of ports (in node:card form) that currently in
# the given vlan.
#
sub getExperimentVlanPorts($) {
    my ($vlanid) = @_;

    my $query_result =
	DBQueryFatal("select members from vlans as v ".
		     "where v.id='$vlanid'");
    return ()
	if (!$query_result->numrows());

    my ($members) = $query_result->fetchrow_array();
    my @members   = split(/\s+/, $members);

    return convertPortsFromIfaces(@members);
}

366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#
# Get the list of stacks that the given set of VLANs *will* or *should* exist
# on
#
sub getPlannedStacksForVlans(@) {
    my @vlans = @_;

    # Get VLAN members, then go from there to devices, then from there to
    # stacks
    my @ports = getVlanPorts(@vlans);
    if ($debug) {
        print "getPlannedStacksForVlans: got ports " . join(",",@ports) . "\n";
    }
    my @devices = getDeviceNames(@ports);
    if ($debug) {
        print("getPlannedStacksForVlans: got devices " . join(",",@devices)
            . "\n");
    }
    my @stacks = getStacksForSwitches(@devices);
    if ($debug) {
        print("getPlannedStacksForVlans: got stacks " . join(",",@stacks) . "\n");
    }
    return @stacks;
}

#
# Get the list of stacks that the given VLANs actually occupy
#
sub getActualStacksForVlans(@) {
    my @vlans = @_;

    # Run through all the VLANs and make a list of the stacks they
    # use
    my @stacks;
    foreach my $vlan (@vlans) {
        my ($vlanobj, $stack);
        if ($debug) {
            print("getActualStacksForVlans: looking up ($vlan)\n");
        }
        if (defined($vlanobj = VLan->Lookup($vlan)) &&
            defined($stack = $vlanobj->GetStack())) {

            if ($debug) {
                print("getActualStacksForVlans: found stack $stack in database\n");
            }
            push @stacks, $stack;
        }
    }
    return uniq(@stacks);
}

417
418
419
420
#
# Update database to store vlan tag.
#
sub setVlanTag ($$) {
421
422
    my ($vlan_id, $tag) = @_;
    
423
    # Silently exit if they passed us no VLANs
424
    if (!$vlan_id || !defined($tag)) {
425
426
427
	return ();
    }

428
429
430
431
432
    my $vlan = VLan->Lookup($vlan_id);
    return ()
	if (!defined($vlan));
    return ()
	if ($vlan->SetTag($tag) != 0);
433
434
435
436

    return 0;
}

437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
#
# Ditto for stack that VLAN exists on
#
sub setVlanStack($$) {
    my ($vlan_id, $stack_id) = @_;
    
    my $vlan = VLan->Lookup($vlan_id);
    return ()
	if (!defined($vlan));
    return ()
	if ($vlan->SetStack($stack_id) != 0);

    return 0;
}

#
# Given a list of VLANs, return only the VLANs that are beleived to actually
# exist on the switches
#
sub filterPlannedVlans(@) {
    my @vlans = @_;
    my @out;
    foreach my $vlan (@vlans) {
        my $vlanobj = VLan->Lookup($vlan);
        if (!defined($vlanobj)) {
            warn "snmpit: Warning, tried to check status of non-existant " .
                "VLAN $vlan\n";
            next;
        }
        if ($vlanobj->CreatedOnSwitches()) {
            push @out, $vlan;
        }
    }
    return @out;
}

473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
#
# Update database to mark port as enabled or disabled.
#
sub setPortEnabled($$) {
    my ($port, $enabled) = @_;

    $port =~ /^(.+):(\d+)$/;
    my ($node, $card) = ($1, $2);
    $enabled = ($enabled ? 1 : 0);

    DBQueryFatal("update interface_state set enabled=$enabled ".
		 "where node_id='$node' and card='$card'");
    
    return 0;
}
# Ditto for trunked.
sub setPortTagged($$) {
    my ($port, $tagged) = @_;

    $port =~ /^(.+):(\d+)$/;
    my ($node, $card) = ($1, $2);
    $tagged = ($tagged ? 1 : 0);

    DBQueryFatal("update interface_state set tagged=$tagged ".
		 "where node_id='$node' and card='$card'");
}

500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
#
# Convert an entire list of ports in port:iface format to into port:card -
# returns other port forms unchanged.
#
sub convertPortsFromIfaces(@) {
    my @ports = @_;
    return map {
        if (/(.+):([A-Za-z].*)/) {
            # Seems to be a node:iface line
            convertPortFromIface($_);
        } else {
            $_;
        }
    } @ports;

}

#
# Convert a port in port:iface format to port:card
#
sub convertPortFromIface($) {
    my ($port) = $_;
    if ($port =~ /(.+):(.+)/) {
523
524
525
	my ($node,$iface) =  ($1,$2);
        my $result = DBQueryFatal("SELECT card, port FROM interfaces " .
				  "WHERE node_id='$node' AND iface='$iface'");
526
527
528
529
        if (!$result->num_rows()) {
            warn "WARNING: convertPortFromIface($port) - Unable to get card\n";
            return $port;
        }
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
        my @row = $result->fetchrow();
        my $card = $row[0];
        my $cport = $row[1];

        $result = DBQueryFatal("SELECT isswitch FROM node_types WHERE type IN ".
                               "(SELECT type FROM nodes WHERE node_id='$node')");

        if (!$result->num_rows()) {
            warn "WARNING: convertPortFromIface($port) -".
                " Uable to decide if $node is a switch or not\n";
            return $port;
        }

        if (($result->fetchrow())[0] == 1) {
	    #
	    # Should return the later one, but many places in snmpit
	    # and this file depend on the old format...
	    #
            return "$node:$card";
            #return "$node:$card.$cport";                                            
        }

552
        return "$node:$card";
553

554
555
556
557
    } else {
        warn "WARNING: convertPortFromIface($port) - Bad port format\n";
        return $port;
    }
558
559
}

560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
#                                                                                    
# If a port is on switch, some port ops in snmpit                                    
# should be avoided.                                                                 
#                                                                                    
sub isSwitchPort($) {
    my $port = shift;

    if ($port =~ /^(.+):(.+)/) {
        my $node = $1;

        my $result = DBQueryFatal("SELECT isswitch FROM node_types WHERE type IN ".
                                  "(SELECT type FROM nodes WHERE node_id='$node')");

        if (($result->fetchrow())[0] == 1) {
            return 1;
        }
    }

    return 0;
}

581
#
582
583
# Returns an array of all VLAN id's used by a given experiment.
# Optional list of vlan ids restricts operation to just those vlans,
584
#
585
586
sub getExperimentVlans ($$@) {
    my ($pid, $eid, @optvlans) = @_;
587

588
589
590
591
592
593
594
595
596
    my $experiment = Experiment->Lookup($pid, $eid);
    if (!defined($experiment)) {
	die("*** $0:\n".
	    "    getExperimentVlans($pid,$eid) - no such experiment\n");
    }
    my @vlans;
    if (VLan->ExperimentVLans($experiment, \@vlans) != 0) {
	die("*** $0:\n".
	    "    Unable to load VLANs for $experiment\n");
597
598
    }

599
600
601
602
603
604
605
    # Convert to how the rest of snmpit wants to see this stuff.
    my @result = ();
    foreach my $vlan (@vlans) {
	push(@result, $vlan->id())
	    if (!@optvlans || grep {$_ == $vlan->id()} @optvlans);
    }
    return @result;
606
607
}

Robert Ricci's avatar
Robert Ricci committed
608
609
610
611
612
613
614
615
616
#
# Returns an array of all ports used by a given experiment
#
sub getExperimentPorts ($$) {
    my ($pid, $eid) = @_;

    return getVlanPorts(getExperimentVlans($pid,$eid));
}

617
618
619
620
621
622
623
624
625
626
627
#
# Returns an array of control net ports used by a given experiment
#
sub getExperimentControlPorts ($$) {
    my ($pid, $eid) = @_;

    # 
    # Get a list of all *physical* nodes in the experiment
    #
    my $exp = Experiment->Lookup($pid,$eid);
    my @nodes = $exp->NodeList(0,0);
628
629
    # plab and related nodes are still in the list, so filter them out
    @nodes = grep {$_->control_iface()} @nodes; 
630
631
632
633
634
635
636
637
638
639
640
641

    #
    # Get control net interfaces
    #
    my @ports =  map { $_->node_id() . ":" . $_->control_iface() } @nodes;

    #
    # Convert from iface to port number when we return
    #
    return convertPortsFromIfaces(@ports);
}

642
643
644
645
646
647
648
649
650
#
# Usage: getDeviceNames(@ports)
#
# Returns an array of the names of all devices used in the given ports
#
sub getDeviceNames(@) {
    my @ports = @_;
    my %devices = ();
    foreach my $port (@ports) {
Robert Ricci's avatar
Robert Ricci committed
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
	#
	# Accept either node:port or switch.port
	#
	my $device;
	if ($port =~ /^([^:]+):(.+)$/) {
	    my ($node,$card) = ($1,$2);
	    if (!defined($node) || !defined($card)) { # Oops, $card can be 0
		die "Bad port given: $port\n";
	    }
	    my $result = DBQueryFatal("SELECT node_id2 FROM wires " .
		"WHERE node_id1='$node' AND card1=$card");
	    if (!$result->num_rows()) {
		warn "No database entry found for port $port - Skipping\n";
		next;
	    }
	    # This is a loop, on the off chance chance that a single port on a
	    # node can be connected to multiple ports on the switch.
	    while (my @row = $result->fetchrow()) {
		$device = $row[0];
	    }
	} elsif ($port =~ /^([^.]+)\.\d+(\/\d+)?$/) {
		$device = $1;
	} else {
	    warn "Invalid format for port $port - Skipping\n";
675
676
	    next;
	}
Robert Ricci's avatar
Robert Ricci committed
677
678

	$devices{$device} = 1;
679
680
681
682

        if ($debug) {
            print "getDevicesNames: Mapping $port to $device\n";
        }
Mac Newbold's avatar
Mac Newbold committed
683
    }
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
    return (sort {tbsort($a,$b)} keys %devices);
}

#
# Returns a hash, keyed by device, of all ports in the given list that are
# on that device
#
sub mapPortsToDevices(@) {
    my @ports = @_;
    my %map = ();
    foreach my $port (@ports) {
	my ($device) = getDeviceNames($port);
	if (defined($device)) { # getDeviceNames does the job of warning users
	    push @{$map{$device}},$port;
	}
699
    }
700
    return %map;
Mac Newbold's avatar
Mac Newbold committed
701
702
}

703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
#
# Returns the device type for the given node_id
#
sub getDeviceType ($) {

    my ($node) = @_;

    my $result =
	DBQueryFatal("SELECT type FROM nodes WHERE node_id='$node'");

    my @row = $result->fetchrow();
    # Sanity check - make sure the node exists
    if (!@row) {
	die "No such node: $node\n";
    }

    return $row[0];
Mac Newbold's avatar
Mac Newbold committed
720
721
}

722
723
724
725
726
727
728
#
# Returns (current_speed,duplex) for the given interface (in node:port form)
#
sub getInterfaceSettings ($) {

    my ($interface) = @_;

729
730
731
732
733
734
735
    #
    # Switch ports are evil and we don't touch them.
    #
    if (isSwitchPort($interface)) {
	return ();
    }

736
737
738
739
740
741
742
    $interface =~ /^(.+):(\d+)$/;
    my ($node, $port) = ($1, $2);
    if ((!defined $node) || (!defined $port)) {
	die "getInterfaceSettings: Bad interface ($interface) given\n";
    }

    my $result =
743
744
745
746
747
748
	DBQueryFatal("SELECT i.current_speed,i.duplex,ic.capval ".
		     "  FROM interfaces as i " .
		     "left join interface_capabilities as ic on ".
		     "     ic.type=i.interface_type and ".
		     "     capkey='noportcontrol' ".
		     "WHERE i.node_id='$node' and i.card=$port");
749
750

    # Sanity check - make sure the interface exists
751
    if ($result->numrows() != 1) {
752
753
	die "No such interface: $interface\n";
    }
754
    my ($speed,$duplex,$noportcontrol) = $result->fetchrow_array();
755

756
757
758
759
760
    # If the port does not support portcontrol, ignore it.
    if (defined($noportcontrol) && $noportcontrol) {
	return ();
    }
    return ($speed,$duplex);
761
762
}

763
764
765
766
767
768
769
770
771
772
773
774
775
776
#
# Returns an array with then names of all switches identified as test switches
#
sub getTestSwitches () {
    my $result =
	DBQueryFatal("SELECT node_id FROM nodes WHERE role='testswitch'");
    my @switches = (); 
    while (my @row = $result->fetchrow()) {
	push @switches, $row[0];
    }

    return @switches;
}

777
778
779
780
781
782
783
784
785
786
787
788
789
790
#
# Returns an array with the names of all switches identified as control switches
#
sub getControlSwitches () {
    my $result =
	DBQueryFatal("SELECT node_id FROM nodes WHERE role='ctrlswitch'");
    my @switches = (); 
    while (my @row = $result->fetchrow()) {
	push @switches, $row[0];
    }

    return @switches;
}

791
792
793
794
795
796
797
798
799
800
801
802
803
804
#
# Returns an array with the names of all switches in the given stack
#
sub getSwitchesInStack ($) {
    my ($stack_id) = @_;
    my $result = DBQueryFatal("SELECT node_id FROM switch_stacks " .
	"WHERE stack_id='$stack_id'");
    my @switches = (); 
    while (my @row = $result->fetchrow()) {
	push @switches, $row[0];
    }

    return @switches;
}
805

806
807
808
809
810
811
812
813
814
815
816
817
818
819
#
# Returns an array with the names of all switches in the given *stacks*, with
# no switches duplicated
#
sub getSwitchesInStacks (@) {
    my @stack_ids = @_;
    my @switches;
    foreach my $stack_id (@stack_ids) {
        push @switches, getSwitchesInStack($stack_id);
    }

    return uniq(@switches);
}

820
#
821
# Returns the stack_id of a switch's primary stack
822
#
823
sub getSwitchPrimaryStack($) {
824
825
    my $switch = shift;
    my $result = DBQueryFatal("SELECT stack_id FROM switch_stacks WHERE " .
826
    		"node_id='$switch' and is_primary=1");
827
    if (!$result->numrows()) {
828
829
830
831
832
	print STDERR "No primary stack_id found for switch $switch\n";
	return undef;
    } elsif ($result->numrows() > 1) {
	print STDERR "Switch $switch is marked as primary in more than one " .
	    "stack\n";
833
834
835
836
837
838
839
	return undef;
    } else {
	my ($stack_id) = ($result->fetchrow());
	return $stack_id;
    }
}

840
841
842
843
844
845
846
847
848
849
850
851
852
853
#
# Returns the stack_ids of the primary stacks for the given switches.
# Surpresses duplicates.
#
sub getStacksForSwitches(@) {
    my (@switches) = @_;
    my @stacks;
    foreach my $switch (@switches) {
        push @stacks, getSwitchPrimaryStack($switch);
    }

    return uniq(@stacks);
}

854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
#
# Returns a list of all stack_ids that a switch belongs to
#
sub getSwitchStacks($) {
    my $switch = shift;
    my $result = DBQueryFatal("SELECT stack_id FROM switch_stacks WHERE " .
    		"node_id='$switch'");
    if (!$result->numrows()) {
	print STDERR "No stack_id found for switch $switch\n";
	return undef;
    } else {
	my @stack_ids;
	while (my ($stack_id) = ($result->fetchrow())) {
	    push @stack_ids, $stack_id;
	}
	return @stack_ids;
    }
}

873
#
Robert Ricci's avatar
Robert Ricci committed
874
# Returns the type of the given stack_id. If called in list context, also
875
876
# returns whether or not the stack supports private VLANs, whether it
# uses a single VLAN domain, and the SNMP community to use.
877
878
879
#
sub getStackType($) {
    my $stack = shift;
880
    my $result = DBQueryFatal("SELECT stack_type, supports_private, " .
881
882
	"single_domain, snmp_community FROM switch_stack_types " .
	"WHERE stack_id='$stack'");
883
884
885
886
    if (!$result->numrows()) {
	print STDERR "No stack found called $stack\n";
	return undef;
    } else {
887
888
	my ($stack_type,$supports_private,$single_domain,$community)
	    = ($result->fetchrow());
Robert Ricci's avatar
Robert Ricci committed
889
	if (defined wantarray) {
890
	    return ($stack_type,$supports_private,$single_domain, $community);
Robert Ricci's avatar
Robert Ricci committed
891
892
893
	} else {
	    return $stack_type;
	}
894
895
896
    }
}

897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
#
# Returns the leader for the given stack - the meaning of this is vendor-
# specific. May be undefined.
#
sub getStackLeader($) {
    my $stack = shift;
    my $result = DBQueryFatal("SELECT leader FROM switch_stack_types " .
	"WHERE stack_id='$stack'");
    if (!$result->numrows()) {
	print STDERR "No stack found called $stack\n";
	return undef;
    } else {
	my ($leader) = ($result->fetchrow());
	return $leader;
    }
}

914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
#
# Get a hash that describes the configuration options for a switch. The idea is
# that the device's object will call this method to get some options.  Right
# now, all this stuff actually comes from the stack, but there could be
# switch-specific configuration in the future. Provides defaults for NULL
# columns
#
# We could probably make this look more like an object, for type checking, but
# that just doesn't seem necessary yet.
#
sub getDeviceOptions($) {
    my $switch = shift;
    my %options;

    my $result = DBQueryFatal("SELECT supports_private, " .
	"single_domain, snmp_community, min_vlan, max_vlan " .
	"FROM switch_stacks AS s left join switch_stack_types AS t " .
	"    ON s.stack_id = t.stack_id ".
	"WHERE s.node_id='$switch'");

    if (!$result->numrows()) {
	print STDERR "No switch $switch found, or it is not in a stack\n";
	return undef;
    }

    my ($supports_private, $single_domain, $snmp_community, $min_vlan,
	$max_vlan) = $result->fetchrow();

    $options{'supports_private'} = $supports_private;
    $options{'single_domain'} = $single_domain;
    $options{'snmp_community'} = $snmp_community || "public";
    $options{'min_vlan'} = $min_vlan || 2;
    $options{'max_vlan'} = $max_vlan || 1000;

    $options{'type'} = getDeviceType($switch);

    if ($debug) {
	print "Options for $switch:\n";
	while (my ($key,$value) = each %options) {
	    print "$key = $value\n"
	}
    }

    return \%options;
}

Robert Ricci's avatar
Robert Ricci committed
960
961
962
963
#
# Returns a structure representing all trunk links. It's a hash, keyed by
# switch, that contains hash references. Each of the second level hashes
# is keyed by destination, with the value being an array reference that
964
965
966
# contains the card.port pairs to which the trunk is conencted. For exammple,
# ('cisco1' => { 'cisco3' => ['1.1','1.2'] },
#  'cisco3' => { 'cisco1' => ['2.1','2.2'] } )
Robert Ricci's avatar
Robert Ricci committed
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
#
sub getTrunks() {

    my %trunks = ();

    my $result = DBQueryFatal("SELECT node_id1, card1, port1, " .
	"node_id2, card2, port2 FROM wires WHERE type='Trunk'");

    while (my @row = $result->fetchrow()) {
	my ($node_id1, $card1, $port1, $node_id2, $card2, $port2)  = @row;
	push @{ $trunks{$node_id1}{$node_id2} }, "$card1.$port1";
	push @{ $trunks{$node_id2}{$node_id1} }, "$card2.$port2";
    }

    return %trunks;
	
}

#
# Find the best path from one switch to another. Returns an empty list if no
# path exists, otherwise returns a list of switch names. Arguments are:
# A reference to a hash, as returned by the getTrunks() function
# A reference to an array of unvisited switches: Use [keys %trunks]
# Two siwtch names, the source and the destination 
991
#
Robert Ricci's avatar
Robert Ricci committed
992
993
994
995
996
997
998
999
1000
sub getTrunkPath($$$$) {
    my ($trunks, $unvisited, $src,$dst) = @_;
    if ($src eq $dst) {
	#
	# The source and destination are the same
	#
	return ($src);
    } elsif ($trunks->{$src}{$dst}) {
	#
For faster browsing, not all history is shown. View entire blame