snmpit_lib.pm 12.9 KB
Newer Older
1
#!/usr/bin/perl -w
Mac Newbold's avatar
Mac Newbold committed
2
#
3
# Module of subroutines useful to snmpit and its modules
Mac Newbold's avatar
Mac Newbold committed
4
5
6
7
8
9
#

package snmpit_lib;

use Exporter;
@ISA = ("Exporter");
10
11
@EXPORT = qw( macport portnum Dev vlanmemb vlanid
		getTestSwitches getVlanPorts getExperimentVlans getDeviceNames
12
	    	getDeviceType getInterfaceSettings mapPortsToDevices
13
14
		getSwitchStack getStackType getTrunks getTrunksFromSwitches
		tbsort );
Mac Newbold's avatar
Mac Newbold committed
15
16

use English;
17
18
use libdb;
use strict;
Mac Newbold's avatar
Mac Newbold committed
19
20
21
22

my $debug = 0;

my %Devices=();
23
# Devices maps device names to device IPs
Mac Newbold's avatar
Mac Newbold committed
24
25

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

my %Ports=();
29
30
31
32
33
34
35
# 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
36

37
38
39
40
41
42
43
#
# Initialize the 
#
sub init($) {
    $debug = shift || $debug;
    &ReadTranslationTable;
    return 0;
Mac Newbold's avatar
Mac Newbold committed
44
45
}

46
47
48
#
# Map between interfaces and mac addresses
#
Mac Newbold's avatar
Mac Newbold committed
49
sub macport {
50
51
    my $val = shift || "";
    return $Interfaces{$val};
Mac Newbold's avatar
Mac Newbold committed
52
53
}

54
55
56
#
# Map between interfaces and port numbers
#
Mac Newbold's avatar
Mac Newbold committed
57
sub portnum {
58
59
    my $val = shift || "";
    return $Ports{$val};
Mac Newbold's avatar
Mac Newbold committed
60
61
}

62
63
64
#
# Map between interfaces and the devices they are attached to
#
Mac Newbold's avatar
Mac Newbold committed
65
sub Dev {
66
67
    my $val = shift || "";
    return $Devices{$val};
Mac Newbold's avatar
Mac Newbold committed
68
69
}

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#
# This function fills in %Interfaces and %Ports
# They hold pcX:Y<==>MAC and pcX:Y<==>switch:port respectively
#
sub ReadTranslationTable {
    my $name="";
    my $mac="";
    my $switchport="";

    print "FILLING %Interfaces\n" if $debug;
    my $result = DBQueryFatal("select * from interfaces;");
    while ( @_ = $result->fetchrow_array()) {
	$name = "$_[0]:$_[1]";
	if ($_[2] != 1) {$name .=$_[2]; }
	$mac = "$_[3]";
	if ($name =~ /(sh\d+)(-\d)(:\d)?/ ) { $name = "$1$3"; }
	$Interfaces{$name} = $mac;
	$Interfaces{$mac} = $name;
	print "Interfaces: $mac <==> $name\n" if $debug > 1;
    }

    print "FILLING %Devices\n" if $debug;
    $result = DBQueryFatal("select i.node_id,i.IP,n.type from interfaces as i ".
	    "left join nodes as n on n.node_id=i.node_id ".
	    "left join node_types as nt on n.type=nt.type ".
	    "where n.role!='testnode' and i.card=nt.control_net");
    while ( my ($name,$ip,$type) = $result->fetchrow_array()) {
	$Devices{$name} = $ip;
	$Devices{$ip} = $name;
	print "Devices: $name ($type) <==> $ip\n" if $debug > 1;
    }

    print "FILLING %Ports\n" if $debug;
    $result = DBQueryFatal("select node_id1,card1,port1,node_id2,card2,port2 ".
	    "from wires;");
    while ( @_ = $result->fetchrow_array()) {
	$name = "$_[0]:$_[1]";
	print "Name='$name'\t" if $debug > 2;
	print "Dev='$_[3]'\t" if $debug > 2;
	$switchport = join(":",($_[3],$_[4]));
	$switchport .=".$_[5]";
	print "switchport='$switchport'\n" if $debug > 2;
	$Ports{$name} = $switchport;
	$Ports{$switchport} = $name;
	print "Ports: '$name' <==> '$switchport'\n" if $debug > 1;
    }

117
118
}

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#
# 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 ();
    }

    my $result = DBQueryFatal("SELECT members FROM vlans WHERE " .
	join(' OR ', map("id='$_'",@vlans))); # Join "id='foo'" with ORs
    my @ports;
    while (my @row = $result->fetchrow()) {
	my $members = $row[0];
	# $members is a space-seprated list
	foreach my $port (split /\s+/,$members) {
	    # Due to the inconsistent nature of our tables (curses!), we
	    # have to do some conversion here
	    $port =~ /^(.+):(.+)/;
	    my ($node,$iface) = ($1,$2);
	    if (!defined($node) || !defined($iface)) {
		warn "WARNING: Bad node in VLAN: $port - skipping\n";
		next;
	    }
	    my $result = DBQueryFatal("SELECT card FROM interfaces " .
	    	"WHERE node_id='$node' AND iface='$iface'");
	    if (!$result->num_rows()) {
		warn "WARNING: Bad node/iface pair in VLAN: $port - skipping\n";
		next;
	    }

	    my $card = ($result->fetchrow())[0];

	    # OK, finally have the info we need
	    push @ports, $node . ":" . $card;
	}
    }
    return @ports;
158
159
}

160
161
162
163
164
165
166
167
168
169
170
171
172
173
#
# Returns an array of all VLAN id's used by a given experiment
#
sub getExperimentVlans ($$) {
    my ($pid, $eid) = @_;

    my $result =
	DBQueryFatal("SELECT id FROM vlans WHERE pid='$pid' AND eid='$eid'");
    my @vlans = (); 
    while (my @row = $result->fetchrow()) {
	push @vlans, $row[0];
    }

    return @vlans;
174
175
}

Robert Ricci's avatar
Robert Ricci committed
176
177
178
179
180
181
182
183
184
#
# Returns an array of all ports used by a given experiment
#
sub getExperimentPorts ($$) {
    my ($pid, $eid) = @_;

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

185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#
# Usage: getDeviceNames(@ports)
#
# Returns an array of the names of all devices used in the given ports
#
sub getDeviceNames(@) {
    my @ports = @_;
    my %devices = ();
    print "getDeviceNames: Passed in " . join(",",@ports) . "\n" if $debug;
    foreach my $port (@ports) {
	$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()) {
	    my $device = $row[0];
	    $devices{$device} = 1;
	}
Mac Newbold's avatar
Mac Newbold committed
212
    }
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
    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;
	}
228
    }
229
    return %map;
Mac Newbold's avatar
Mac Newbold committed
230
231
}

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#
# 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
249
250
}

251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#
# Returns (current_speed,duplex) for the given interface (in node:port form)
#
sub getInterfaceSettings ($) {

    my ($interface) = @_;

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

    my $result =
	DBQueryFatal("SELECT current_speed, duplex FROM interfaces " .
		     "WHERE node_id='$node' and card=$port");

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

    return @row;
}

277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#
# 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];
    }

    # Sanity check - make sure we actually found some
    if (!@switches) {
	die "Failed to find testswitches\n";
    }

    return @switches;
}

#
# Returns the stack_id that a switch belongs to
#
sub getSwitchStack($) {
    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_id) = ($result->fetchrow());
	return $stack_id;
    }
}

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#
# Returns the type of the given stack_id
#
sub getStackType($) {
    my $stack = shift;
    my $result = DBQueryFatal("SELECT stack_type FROM switch_stack_types WHERE " .
    		"stack_id='$stack'");
    if (!$result->numrows()) {
	print STDERR "No stack found called $stack\n";
	return undef;
    } else {
	my ($stack_type) = ($result->fetchrow());
	return $stack_type;
    }
}

Robert Ricci's avatar
Robert Ricci committed
328
329
330
331
#
# 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
332
333
334
# 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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#
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 
359
#
Robert Ricci's avatar
Robert Ricci committed
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
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
sub getTrunkPath($$$$) {
    my ($trunks, $unvisited, $src,$dst) = @_;
    if ($src eq $dst) {
	#
	# The source and destination are the same
	#
	return ($src);
    } elsif ($trunks->{$src}{$dst}) {
	#
	# The source and destination are directly connected
	#
	return ($src,$dst);
    } else {
	# The source and destination aren't directly connected. We'll need to 
	# recurse across other trunks to find solution
	my @minPath = ();

	#
	# We use the @$unvisited list to pick switches to traverse to, so
	# that we don't re-visit switches we've already been to, which would 
	# cause infinite recursion
	#
	foreach my $i (0 .. $#{$unvisited}) {
	    if ($trunks->{$src}{$$unvisited[$i]}) {

		#
		# We need to pull theswitch out of the unvisted list that we
		# pass to it.
		#
		my @list = @$unvisited;
		splice(@list,$i,1);

		#
		# Check to see if the path we get with this switch is the 
		# best one found so far
		#
		my @path = getTrunkPath($trunks,\@list,$$unvisited[$i],$dst);
		if (@path && ((!@minPath) || (@path < @minPath))) {
		    @minPath = @path;
		}
	    }

	}

	#
	# If we found a path, tack ourselves on the front and return. If not,
	# return the empty list of failure.
	#
	if (@minPath) {
	    return ($src,@minPath);
	} else {
	    return ();
	}
    }
}

416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
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
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
500
501
502
#
# Returns a list of trunks, in the form [src, dest], from a path (as returned
# by getTrunkPath() ). For example, if the input is:
# (cisco1, cisco3, cisco4), the return value is:
# ([cisco1, cisco3], [cisco3, cisco4])
#
sub getTrunksFromPath(@) {
    my @path = @_;
    my @trunks = ();
    my $lastswitch = "";
    foreach my $switch (@path) {
	if ($lastswitch) {
	    push @trunks, [$lastswitch, $switch];
	}
	$lastswitch = $switch;
    }

    return @trunks;
}

#
# Given a list of lists of trunks (returned by multiple getTrunksFromPath() 
# calls), return a list of the unique trunks found in this list
#
sub getUniqueTrunks(@) {
    my @trunkLists = @_;
    my @unique = ();
    foreach my $trunkref (@trunkLists) {
	my @trunks = @$trunkref;
	TRUNK: foreach my $trunk (@trunks) {
	    # Since source and destination are interchangable, we have to
	    # check both possible orderings
	    foreach my $unique (@unique) {
		if ((($unique->[0] eq $trunk->[0]) &&
		     ($unique->[1] eq $trunk->[1])) ||
		    (($unique->[0] eq $trunk->[1]) &&
		     ($unique->[1] eq $trunk->[0]))) {
			 # Yep, it's already in the list - go to the next one
			 next TRUNK;
		}
	    }

	    # Made it through, we must not have seen this one before
	    push @unique, $trunk;
	}
    }

    return @unique;
}

#
# Given a trunk structure (as returned by getTrunks() ), and a list of switches,
# return a list of all trunks (in the [src, dest] form) that are needed to span
# all the switches (ie. which trunks the VLAN must be allowed on)
#
sub getTrunksFromSwitches($@) {
    my $trunks = shift;
    my @switches = @_;

    #
    # First, find the paths between each set of switches
    #
    my @paths = ();
    foreach my $switch1 (@switches) {
	foreach my $switch2 (@switches) {
	    push @paths, [ getTrunkPath($trunks, [ keys %$trunks ],
					$switch1, $switch2) ];
	}
    }

    #
    # Now, make a list of all the the trunks used by these paths
    #
    my @trunkList = ();
    foreach my $path (@paths) {
	push @trunkList, [ getTrunksFromPath(@$path) ];
    }

    #
    # Last, remove any duplicates from the list of trunks
    #
    my @trunks = getUniqueTrunks(@trunkList);

    return @trunks;

}

503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
#
# Used to sort a set of nodes in testbed order (ie. pc2 < pc10)
#
# usage: tbsort($a,$b)
#        returns -1 if $a < $b
#        returns  0 if $a == $b
#        returns  1 if $a > $b
#
sub tbsort { 
    my ($a,$b) = @_;
    $a =~ /^([a-z]*)([0-9]*):?([0-9]*)/;
    my $a_let = ($1 || "");
    my $a_num = ($2 || 0);
    my $a_num2 = ($3 || 0);
    $b =~ /^([a-z]*)([0-9]*):?([0-9]*)/;
    my $b_let = ($1 || "");
    my $b_num = ($2 || 0);
    my $b_num2 = ($3 || 0);
    if ($a_let eq $b_let) {
	if ($a_num == $b_num) {
	    return $a_num2 <=> $b_num2;
	} else {
	    return $a_num <=> $b_num;
	}
    } else {
	return $a_let cmp $b_let;
    }
    return 0;
}
Mac Newbold's avatar
Mac Newbold committed
532
533
534
# End with true
1;