rename upgrade file to include switch model N1148T-ON
[dell-switch] / sbw-parse.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 use autodie;
5
6 # ./sw-name-mac.sh
7
8 # usage: NAME_MAC=/dev/shm/file-with-name-space-mac sbw-parse.pl [optional-switch-snmpbulkwalk-dump]
9
10 use Data::Dump qw(dump);
11
12 $|=1; # flush stdout
13
14 my $debug = $ENV{DEBUG} || 0;
15
16 my @cols = qw( ifName ifHighSpeed ifAdminStatus ifOperStatus ifType dot1dStpPortPathCost ifAlias );
17
18 my $mac2name;
19
20 foreach my $name_mac ( qw( /dev/shm/sw-name-mac /dev/shm/wap-name-mac ), $ENV{NAME_MAC} ) {
21         next unless -e $name_mac;
22         open(my $f, '<'. $name_mac);
23         while(<$f>) {
24                 chomp;
25                 #my ( $ip, $name, $mac ) = split(/ /,$_);
26                 my ( $name, $mac ) = split(/ /,$_);
27                 $mac = lc($mac);
28                 $mac2name->{$mac} = $name;
29         }
30 }
31
32 sub mac2name {
33         my ( $mac, $name ) = @_;
34
35         $mac = lc($mac);
36
37         if ( exists $mac2name->{$mac} ) {
38                 my $mac_name = $mac2name->{$mac};
39                 warn "ERROR: name different $name != $mac_name" if $name && $name ne $mac_name;
40                 return ( $mac, $mac_name );
41         }
42         return ( $mac, $name );
43 }
44
45 warn "# mac2name = ",dump($mac2name) if $debug;
46
47
48 my $sw;
49
50 sub fix_mac {
51         my $mac = shift;
52         $mac = lc($mac);
53         $mac =~ s/^([0-9a-f]):/0$1:/;
54         while ( $mac =~ s/:([0-9a-f]):/:0$1:/g ) {};
55         $mac =~ s/:([0-9a-f])$/:0$1/;
56 #       warn "# $mac\n";
57         return $mac;
58 }
59
60 sub sw_name_mac_port {
61         my ( $name, $mac, $port ) = @_;
62         $mac = fix_mac($mac);
63         if ( exists $mac2name->{$mac} ) {
64                 my $sw_name = $mac2name->{$mac};
65                 $sw->{$name}->{port_for_switch}->{$port}->{ $sw_name }++;
66                 #print "## $name $port $sw_name\n";
67         }
68 }
69
70 my $gv; # collect for graphviz
71
72 my @files = @ARGV;
73 @files = glob('snmpbulkwalk/*') unless @files;
74
75 foreach my $file ( @files ) {
76         my ( undef, $name ) = split(/\//, $file);
77         print "# $name $file\n" if $debug;
78
79         if ( -s "/dev/shm/$file" ) {
80                 if ( -M $file < -M "/dev/shm/$file" ) {
81                         warn "UPDATE $file\n";
82                         system "cp -pv /dev/shm/$file $file";
83                 }
84                 $file="/dev/shm/$file";
85         } else {
86                 warn "WARNING: using old file $file\n";
87         }
88
89         open(my $f, '<', $file);
90         while(<$f>) {
91                 chomp;
92                 if ( m/::(sysName|sysLocation|ipDefaultTTL|dot1dStpPriority|dot1dStpTopChanges|dot1dStpDesignatedRoot|dot1dStpRootCost|dot1dStpRootPort|dot1qNumVlans)\./ ) {
93                         my ( undef, $v ) = split(/ = \w+: /,$_,2);
94                         $sw->{$name}->{$1} = $v;
95                 } elsif ( m/::(ifMtu|ifHighSpeed|ifSpeed)\[(\d\d?)\] = (?:INTEGER|Gauge32): (\d+)/ ) {
96                         $sw->{$name}->{$1}->[$2] = $3;
97                         # Dell PowerConnect 5548 doeesn't have ifHighSpeed
98                         if ( $1 eq 'ifSpeed' && ! exists $sw->{$name}->{'ifHighSpeed'}->[$2] ) {
99                                 $sw->{$name}->{'ifHighSpeed'}->[$2] = $3 / 1_000_000;
100                         }
101                 } elsif ( m/::(ifPhysAddress)\[(\d\d?)\] = STRING: ([0-9a-f:]+)/ ) {
102                         $sw->{$name}->{$1}->[$2] = fix_mac($3);
103                 } elsif ( m/::(ifDescr|ifName|ifAlias)\[(\d\d?)\] = STRING: (.+)/ ) {
104                         $sw->{$name}->{$1}->[$2] = $3;
105                         if ( $1 eq 'ifName' ) {
106                                 my ( $if_name, $port ) = ($3,$2);
107                                 $sw->{$name}->{port_name_to_number}->{$if_name} = $port;
108                         }
109                         if ( $1 eq 'ifDescr' ) { # some switches miss ifName
110                                 my ( $if_name, $port ) = ($3,$2);
111                                 $if_name =~ s/gigabitethernet/ge/;
112                                 $if_name =~ s/tengigabitethernet/te/;
113                                 $sw->{$name}->{'ifName'}->[$port] = $if_name
114                                         if ( ! exists $sw->{name}->{'ifName'} );
115                                 $sw->{$name}->{port_name_to_number}->{$if_name} = $port 
116                                         if ( ! exists $sw->{$name}->{port_name_to_number}->{$if_name} );
117                         }
118                 } elsif ( m/::(ifAdminStatus|ifOperStatus|ifType|dot3StatsDuplexStatus)\[(\d\d?)\] = INTEGER: (\w+)\(/ ) {
119                         $sw->{$name}->{$1}->[$2] = $3;
120                 } elsif ( m/::(dot1dStpPortPathCost)\[(\d\d?)\] = INTEGER: (\d+)/ ) {
121                         $sw->{$name}->{$1}->[$2] = $3;
122                 } elsif ( m/::(dot1dTpFdbPort)\[STRING: ([0-9a-f:]+)\] = INTEGER: (\d+)/ ) {
123                         $sw->{$name}->{$1}->{ fix_mac($2) } = $3;
124                         sw_name_mac_port( $name, $2, $3 );
125                 } elsif ( m/::(dot1qTpFdbPort)\[(\d+)\]\[STRING: ([0-9a-f:]+)\] = INTEGER: (\d+)/ ) {
126                         $sw->{$name}->{$1}->{ fix_mac($3) } = [ $4, $2 ]; # port, vlan
127                         sw_name_mac_port( $name, $3, $4 );
128                 }
129
130                 # dot1qVlanCurrentEgressPorts
131                 # dot1qVlanCurrentUntaggedPorts
132                 # dot1qVlanStaticName
133                 # dot1qVlanStaticEgressPorts
134                 # dot1qVlanStaticUntaggedPorts
135                 # dot1qPvid
136
137                 # entPhysicalDescr
138                 # entPhysicalClass
139
140                 #print "## $_<--\n";
141         }
142         warn "# sw $name = ",dump($sw->{$name}) if $debug;
143
144         foreach my $port ( 1 .. $#{ $sw->{$name}->{ifDescr} } ) {
145                 print "$name $port";
146                 foreach my $oid ( @cols ) {
147                         if ( $oid eq 'ifAlias' ) {
148                                 if ( defined( $sw->{$name}->{$oid}->[$port] ) ) {
149                                         print " [",$sw->{$name}->{$oid}->[$port],"]";
150                                 }
151                         } elsif ( defined $sw->{$name}->{$oid}->[$port] ) {
152                                 print " ", $sw->{$name}->{$oid}->[$port];
153                         } else {
154                                 print " ?";
155                                 #warn "MISSING $name $oid $port\n";
156                         }
157
158                 }
159                 if ( exists( $sw->{$name}->{port_for_switch}->{ $port } ) ) {
160                         my @visible = sort keys %{ $sw->{$name}->{port_for_switch}->{ $port } };
161                         print " ",join(',', @visible);
162
163                         if ( scalar @visible == 1 ) {
164                                 $gv->{$name}->{$port}->{ $visible[0] }->{ 'no_port' } = [$port,0]; # no port
165                         }
166                 }
167                 print "\n";
168         }}
169
170 # fix ifPhysAddress
171
172 # read neighbour visibility from lldp
173
174 sub port2number {
175         my ($name,$port) = @_;
176
177         return $port if $port =~ m/^\d+$/;
178         $port =~ s{bridge\d*/}{};  # remove mikrotik port prefix
179         $port =~ s{,bridge\d*$}{}; # remove mikrotik port suffix
180         $port =~ s{,bonding\d*$}{}; # remove mikrotik port suffix
181
182         if ( exists $sw->{$name}->{port_name_to_number}->{$port} ) {
183                 return $sw->{$name}->{port_name_to_number}->{$port};
184         }
185
186         # gigabitethernet1/0/45 or gi1/0/45
187         if ( $port =~ m{(.+)1/0/(\d+)$} ) {
188                 my ($type,$port) = ($1,$2);
189                 if ( $type =~ m{gi}i ) {
190                         return $port;
191                 } elsif ( $type =~ m{te}i ) {
192                         return $port <= 4 ? $port + 10000 : $port;
193                 } else {
194                         die "unknown [$type]";
195                 }
196         }
197
198         # linux
199         if ( $port =~ m{eth(\d+)} ) {
200                 return $1;
201         }
202
203         my @fuzzy = grep { m/^$port/ } keys %{ $sw->{$name}->{port_name_to_number} };
204         if ( scalar @fuzzy == 1 ) {
205                 return $sw->{$name}->{port_name_to_number}->{$fuzzy[0]}
206         }
207
208         warn "ERROR [$_] can't find port $port for $name in ",dump( $sw->{$name}->{port_name_to_number} );
209 }
210
211 sub fix_sw_name {
212         my $name = shift;
213         if ( $name eq 'rack3-lib' ) {
214                 $name = 'sw-lib-srv';
215         }
216         return $name;
217 }
218
219
220 open(my $n_fh, '<', '/dev/shm/neighbors.tab');
221 while(<$n_fh>) {
222         chomp;
223         my ( $sw1, $port1, undef, $port2, $sw2, undef ) = split(/\t/, $_, 6 );
224         next if $port2 =~ m/:/; # skip mac in port number (wap lldp leek)
225         next unless $sw2 && $port2;
226         $sw1 = fix_sw_name($sw1);
227         my $port1_nr = port2number( $sw1, $port1 );
228         my $port2_nr = port2number( $sw2, $port2 );
229
230         if ( $sw->{$sw1}->{ifType}->[$port1_nr] ne 'ethernetCsmacd' ) {
231                 warn "SKIP $sw1 $port1_nr $sw->{$sw1}->{ifType}->[$port1_nr]\n";
232                 next;
233         } elsif ( $port1 =~ m{bridge} ) { # skip mikrotik bridges
234 #               warn "SKIP $sw1 $port1_nr $port1\n";
235 #               next;
236         }
237
238         $gv->{$sw1}->{$port1_nr}->{$sw2}->{$port2_nr} = [ $port1, $port2 ];
239         delete $gv->{$sw1}->{$port1_nr}->{$sw2}->{'no_port'} if exists $gv->{$sw1}->{$port1_nr}->{$sw2}->{'no_port'};
240 }
241
242 # FIXME sw-b101 doesn't have lldp so we insert data here manually from pictures
243 sub fake_gv {
244         my ($sw1, $p1, $sw2, $p2) = @_;
245         $gv->{$sw1}->{$p1}->{$sw2}->{$p2} = [ $p1, $p2 ];
246         $gv->{$sw2}->{$p2}->{$sw1}->{$p1} = [ $p2, $p1 ];
247 }
248 delete $gv->{'sw-b101'};
249 fake_gv( 'sw-b101' => 3, 'sw-b101' => 4 ); # vlan1-to-vlan2 / vlan2-to-vlan1
250
251 fake_gv( 'sw-b101' => 21, 'sw-rack2' => 50 );
252 fake_gv( 'sw-b101' => 23, 'sw-lib-srv' => 48 );
253 fake_gv( 'sw-b101' => 24, 'sw-rack1' => 48 );
254
255 #delete $gv->{'sw-ganeti'};
256 # spf-16 -> "sw-rack2"        => { 2 => ["16-sfp-uplink-b101,bridge", "te1/0/2"] },
257
258 print "# gv = ",dump( $gv );
259
260 open(my $dot_fh, '>', '/tmp/network.dot');
261 print $dot_fh qq|
262 digraph topology {
263 graph [ rankdir = LR ]
264 node [ shape = record ]
265 edge [ color = "gray" ]
266 |;
267
268 my @edges;
269 my $node;
270
271 foreach my $sw1 ( sort keys %$gv ) {
272         foreach my $p1 ( sort { $a <=> $b } keys %{ $gv->{$sw1} } ) {
273                 foreach my $sw2 ( sort keys %{ $gv->{$sw1}->{$p1} } ) {
274                         if ( $sw1 eq $sw2 ) {
275                                 warn "SKIP same switch $sw1 == $sw2\n";
276                                 next;
277                         }
278                         #push @{ $node->{$sw1} }, [ $p1, $sw2 ];
279                         foreach my $p2 ( keys %{ $gv->{$sw1}->{$p1}->{$sw2} } ) {
280                                 push @edges, [ $sw1, $sw2, $p1, $p2 ];
281                                 ##push @{ $node->{$sw2} }, [ $p2, $sw1 ];
282                                 push @{ $node->{$sw1} }, [ $p1, $sw2, $p2 ];
283                         }
284                 }
285         }
286 }
287
288 foreach my $n ( keys %$node ) {
289         no warnings;
290         my @port_sw =
291                 sort { $a->[0] <=> $b->[0] }
292                 @{ $node->{$n} };
293         print $dot_fh qq!"$n" [ label="!.uc($n).'|' . join('|', map {
294                 sprintf "<%d>%2d %s%s", $_->[0], $_->[0], $_->[1], $_->[2] eq 'no_port' ? '' : ' ' . $_->[2]
295         } @port_sw ) . qq!" ];\n!;
296 }
297
298 foreach my $e ( @edges ) {
299         no warnings;
300         print $dot_fh sprintf qq{ "%s":%d -> "%s":%d\n}, $e->[0], $e->[2], $e->[1], $e->[3];
301 }
302
303 print $dot_fh qq|
304 }
305 |;
306
307 system "dot -Tsvg /tmp/network.dot > /var/www/network.svg";