1# SNMP::Info::Layer3::Timetra
2#
3# Copyright (c) 2008 Bill Fenner
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are met:
7#
8#     * Redistributions of source code must retain the above copyright notice,
9#       this list of conditions and the following disclaimer.
10#     * Redistributions in binary form must reproduce the above copyright
11#       notice, this list of conditions and the following disclaimer in the
12#       documentation and/or other materials provided with the distribution.
13#     * Neither the name of the University of California, Santa Cruz nor the
14#       names of its contributors may be used to endorse or promote products
15#       derived from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21# LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28
29package SNMP::Info::Layer3::Timetra;
30
31use strict;
32use warnings;
33use Exporter;
34use SNMP::Info::Layer3;
35use SNMP::Info::Aggregate;
36
37@SNMP::Info::Layer3::Timetra::ISA
38    = qw/SNMP::Info::Aggregate SNMP::Info::Layer3
39    Exporter/;
40@SNMP::Info::Layer3::Timetra::EXPORT_OK = qw//;
41
42our ($VERSION, %GLOBALS, %MIBS, %FUNCS, %MUNGE);
43
44$VERSION = '3.81';
45
46%MIBS = (
47    %SNMP::Info::Layer3::MIBS,
48    %SNMP::Info::Aggregate::MIBS,
49    'TIMETRA-GLOBAL-MIB'  => 'timetraReg',
50    'TIMETRA-LLDP-MIB'    => 'tmnxLldpAdminStatus',
51    'TIMETRA-PORT-MIB'    => 'tmnxPortEtherDuplex',
52    'TIMETRA-CHASSIS-MIB' => 'tmnxChassisFanOperStatus',
53);
54
55%GLOBALS = ( %SNMP::Info::Layer3::GLOBALS, );
56
57%FUNCS = (
58    %SNMP::Info::Layer3::FUNCS,
59
60    # For some reason LLDP-MIB::lldpLocManAddrTable is populated
61    # but LLDP-MIB::lldpRemTable is not and we need to use the
62    # proprietary TIMETRA-LLDP-MIB Note: these tables are
63    # indexed differently than LLDP-MIB
64    # TIMETRA-LLDP-MIB::tmnxLldpRemTable
65    'lldp_rem_id_type'  => 'tmnxLldpRemChassisIdSubtype',
66    'lldp_rem_id'       => 'tmnxLldpRemChassisId',
67    'lldp_rem_pid_type' => 'tmnxLldpRemPortIdSubtype',
68    'lldp_rem_pid'      => 'tmnxLldpRemPortId',
69    'lldp_rem_desc'     => 'tmnxLldpRemPortDesc',
70    'lldp_rem_sysname'  => 'tmnxLldpRemSysName',
71    'lldp_rem_sysdesc'  => 'tmnxLldpRemSysDesc',
72    'lldp_rem_sys_cap'  => 'tmnxLldpRemSysCapEnabled',
73    'lldp_rem_cap_spt'  => 'tmnxLldpRemSysCapSupported',
74
75    # TIMETRA-LLDP-MIB::tmnxLldpRemManAddrTable
76    'lldp_rman_addr' => 'tmnxLldpRemManAddrIfSubtype',
77
78    # TIMETRA-PORT-MIB::tmnxPortEtherTable
79    'tmnx_eth_speed_admin'  => 'tmnxPortEtherSpeed',
80    'tmnx_eth_duplex'       => 'tmnxPortEtherOperDuplex',
81    'tmnx_eth_duplex_admin' => 'tmnxPortEtherDuplex',
82    'tmnx_eth_auto'         => 'tmnxPortEtherAutoNegotiate',
83
84    # TIMETRA-CHASSIS-MIB::tmnxChassisFanTable
85    'tmnx_fan_state' => 'tmnxChassisFanOperStatus',
86
87    # TIMETRA-CHASSIS-MIB::tmnxChassisPowerSupplyTable
88    'tmnx_ps1_state' => 'tmnxChassisPowerSupply1Status',
89    'tmnx_ps2_state' => 'tmnxChassisPowerSupply2Status',
90
91    # TIMETRA-CHASSIS-MIB::tmnxHwTable
92    'e_descr'  => 'tmnxHwName',
93    'e_parent' => 'tmnxHwContainedIn',
94    'e_name'   => 'tmnxHwName',
95    'e_class'  => 'tmnxHwClass',
96    'e_pos'    => 'tmnxHwParentRelPos',
97    'e_swver'  => 'tmnxHwSoftwareCodeVersion',
98    'e_model'  => 'tmnxHwMfgBoardNumber',
99    'e_serial' => 'tmnxHwSerialNumber',
100    'e_fru'    => 'tmnxHwIsFRU',
101    'e_fwver'  => 'tmnxHwFirmwareCodeVersion',
102);
103
104%MUNGE = (
105    %SNMP::Info::Layer3::MUNGE,
106    'tmnx_fan_state' => \&SNMP::Info::Layer3::Timetra::munge_tmnx_state,
107    'tmnx_ps1_state' => \&SNMP::Info::Layer3::Timetra::munge_tmnx_state,
108    'tmnx_ps2_state' => \&SNMP::Info::Layer3::Timetra::munge_tmnx_state,
109    'e_type'         => \&SNMP::Info::munge_e_type,
110    'e_class'        => \&SNMP::Info::Layer3::Timetra::munge_tmnx_e_class,
111    'e_swver'        => \&SNMP::Info::Layer3::Timetra::munge_tmnx_e_swver,
112);
113
114sub model {
115    my $timetra = shift;
116    my $id      = $timetra->id();
117    my $model   = SNMP::translateObj($id);
118    my $descr   = $timetra->description();
119
120    my $str;
121
122    if ( defined ($descr) && $descr =~ /\s+(7\d{3})/ ) {
123        $str = $1;
124    }
125
126    if ( defined $model && $model =~ /^tmnxModel/ ) {
127        $model =~ s/^tmnxModel//;
128        $model =~ s/Reg$//;
129        $str .= $str ? " " : "";
130        $str .= $model;
131    }
132
133    return $str || $id;
134}
135
136sub os {
137    return 'TiMOS';
138}
139
140sub vendor {
141    return 'nokia';
142}
143
144sub os_ver {
145    my $timetra = shift;
146
147    my $descr = $timetra->description();
148    if ( defined ($descr) && $descr =~ m/^TiMOS-(\S+)/x ) {
149        return $1;
150    }
151    return;
152}
153
154# The interface description contains the SFP type, so
155# to avoid losing historical information through a configuration change
156# we use interface name instead.
157sub interfaces {
158    my $alu     = shift;
159    my $partial = shift;
160
161    return $alu->orig_i_name($partial);
162}
163
164# The TIMETRA-LLDP-MIB::tmnxLldpRemTable unambiguously states it uses ifIndex
165# Trying to cross reference to ifDescr or ifAlias would cause unpredictable
166# results based upon how the device names ports.
167sub lldp_if {
168    my $alu     = shift;
169    my $partial = shift;
170
171    my $addr = $alu->lldp_rem_pid($partial) || {};
172
173    my %lldp_if;
174    foreach my $key ( keys %$addr ) {
175        my @aOID = split( '\.', $key );
176        my $port = $aOID[1];
177        next unless $port;
178
179        $lldp_if{$key} = $port;
180    }
181    return \%lldp_if;
182}
183
184# The proprietary TIMETRA-LLDP-MIB tables are indexed differently than LLDP-MIB
185# We overwrite the private function so that the we don't have to replicate
186# the code in SNMP::Info::LLDP that uses it.
187
188sub _lldp_addr_index {
189    my $alu = shift;
190    my $idx = shift;
191
192    my @oids = split( /\./, $idx );
193
194    # Index has extra field compared to LLDP-MIB
195    my $index = join( '.', splice( @oids, 0, 4 ) );
196    my $proto = shift(@oids);
197    shift(@oids) if scalar @oids > 4;    # $length
198
199    # IPv4
200    if ( $proto == 1 ) {
201        return ( $index, $proto, join( '.', @oids ) );
202    }
203
204    # IPv6
205    elsif ( $proto == 2 ) {
206        return ( $index, $proto,
207            join( ':', unpack( '(H4)*', pack( 'C*', @oids ) ) ) );
208    }
209
210    # MAC
211    elsif ( $proto == 6 ) {
212        return ( $index, $proto,
213            join( ':', map { sprintf "%02x", $_ } @oids ) );
214    }
215
216    # TODO - Other protocols may be used as well; implement when needed?
217    else {
218        return;
219    }
220};
221
222sub i_duplex {
223    my $alu     = shift;
224    my $partial = shift;
225
226    my $hw_duplex = $alu->tmnx_eth_duplex($partial) || {};
227
228    my %i_duplex;
229    if ( ref {} eq ref $hw_duplex and scalar keys %$hw_duplex ) {
230        foreach my $if ( keys %$hw_duplex ) {
231            my $duplex = $hw_duplex->{$if};
232            next unless defined $duplex;
233            next if $duplex eq 'notApplicable';
234            my ( $slot, $ifindex ) = split( /\./, $if );
235
236            $duplex = 'half'
237                if ( $duplex =~ /half/i );
238            $duplex = 'full'
239                if ( $duplex =~ /full/i );
240
241            $i_duplex{$ifindex} = $duplex;
242        }
243        return \%i_duplex;
244    }
245    return $alu->SUPER::i_duplex($partial);
246}
247
248sub i_duplex_admin {
249    my $alu     = shift;
250    my $partial = shift;
251
252    my $hw_duplex_admin = $alu->tmnx_eth_duplex_admin($partial) || {};
253    my $hw_auto         = $alu->tmnx_eth_auto($partial)         || {};
254
255    my %i_duplex_admin;
256    foreach my $if ( keys %$hw_duplex_admin ) {
257        my $duplex = $hw_duplex_admin->{$if};
258        next unless defined $duplex;
259        next if $duplex eq 'notApplicable';
260        my $auto = $hw_auto->{$if} || 'false';
261        my ( $slot, $ifindex ) = split( /\./, $if );
262
263        my $string = 'other';
264        $string = 'half'
265            if ( $duplex =~ /half/i and $auto =~ /false/i );
266        $string = 'full'
267            if ( $duplex =~ /full/i and $auto =~ /false/i );
268        $string = 'auto' if $auto =~ /true/i;
269
270        $i_duplex_admin{$ifindex} = $string;
271    }
272    return \%i_duplex_admin;
273}
274
275sub agg_ports {
276    my $alu = shift;
277
278    return $alu->agg_ports_ifstack();
279}
280
281sub fan {
282    my $alu = shift;
283
284    my $state = $alu->tmnx_fan_state() || {};
285
286    if ( scalar keys %$state ) {
287        my @messages = ();
288
289        foreach my $k ( keys %$state ) {
290            next if $state->{$k} and $state->{$k} eq 'Ok';
291            my ( $chassis, $fan ) = split( /\./, $k );
292            push @messages, "Fan $fan, Chassis $chassis: $state->{$k}";
293        }
294
295        push @messages, ( ( scalar keys %$state ) . " fans OK" )
296            if scalar @messages == 0;
297
298        return ( join ", ", @messages );
299    }
300    return;
301}
302
303sub ps1_status {
304    my $alu = shift;
305
306    my $pwr_state = $alu->tmnx_ps1_state() || {};
307
308    my $ret = "";
309    my $s   = "";
310    foreach my $i ( sort keys %$pwr_state ) {
311        my ( $chassis, $num ) = split( /\./, $i );
312        $ret
313            .= $s
314            . "Chassis "
315            . $chassis . " PS "
316            . $num . ": "
317            . $pwr_state->{$i};
318        $s = ", ";
319    }
320    return if ( $s eq "" );
321    return $ret;
322}
323
324sub ps2_status {
325    my $alu = shift;
326
327    my $pwr_state = $alu->tmnx_ps2_state() || {};
328
329    my $ret = "";
330    my $s   = "";
331    foreach my $i ( sort keys %$pwr_state ) {
332        my ( $chassis, $num ) = split( /\./, $i );
333        $ret
334            .= $s
335            . "Chassis "
336            . $chassis . " PS "
337            . $num . ": "
338            . $pwr_state->{$i};
339        $s = ", ";
340    }
341    return if ( $s eq "" );
342    return $ret;
343}
344
345sub e_index {
346    my $alu  = shift;
347    my $partial = shift;
348
349    # Use MIB leaf to force load here
350    my $e_descr = $alu->tmnxHwID($partial);
351
352    return unless ( ref {} eq ref $e_descr and scalar keys %$e_descr );
353
354    my %e_index;
355
356    foreach my $iid ( keys %$e_descr ) {
357        $e_index{$iid} = $iid;
358    }
359    return \%e_index;
360}
361
362sub munge_tmnx_state {
363    my $state = shift;
364
365    $state =~ s/deviceState//;
366    $state =~ s/device//;
367    return $state;
368}
369
370sub munge_tmnx_e_class {
371    my $class = shift;
372
373    if ($class eq 'physChassis') {
374        $class = 'chassis';
375    }
376    elsif ($class =~ /Module/i) {
377        $class = 'module';
378    }
379    return $class;
380}
381
382sub munge_tmnx_e_swver {
383    my $swver = shift;
384
385    if ( $swver =~ m/^TiMOS-(\S+)/x ) {
386        return $1;
387    }
388    return $swver;
389}
390
3911;
392__END__
393
394=head1 NAME
395
396SNMP::Info::Layer3::Timetra - SNMP Interface to Alcatel-Lucent SR
397
398=head1 AUTHOR
399
400Bill Fenner
401
402=head1 SYNOPSIS
403
404 # Let SNMP::Info determine the correct subclass for you.
405 my $alu = new SNMP::Info(
406                        AutoSpecify => 1,
407                        Debug       => 1,
408                        # These arguments are passed directly to SNMP::Session
409                        DestHost    => 'myswitch',
410                        Community   => 'public',
411                        Version     => 2
412                        )
413    or die "Can't connect to DestHost.\n";
414
415 my $class      = $alu->class();
416 print "SNMP::Info determined this device to fall under subclass : $class\n";
417
418=head1 DESCRIPTION
419
420Subclass for Alcatel-Lucent Service Routers
421
422=head2 Inherited Classes
423
424=over
425
426=item SNMP::Info::Layer3
427
428=back
429
430=head2 Required MIBs
431
432=over
433
434=item F<TIMETRA-GLOBAL-MIB>
435
436=item F<TIMETRA-LLDP-MIB>
437
438=item F<TIMETRA-PORT-MIB>
439
440=item F<TIMETRA-CHASSIS-MIB>
441
442=item Inherited Classes' MIBs
443
444See L<SNMP::Info::Layer3/"Required MIBs"> for its own MIB requirements.
445
446=back
447
448=head1 GLOBALS
449
450These are methods that return scalar value from SNMP
451
452=over
453
454=item $alu->vendor()
455
456Returns 'nokia'
457
458=item $alu->os()
459
460Returns 'TiMOS'
461
462=item $alu->os_ver()
463
464Grabs the version string from C<sysDescr>.
465
466=item $alu->model()
467
468Tries to combine series and model extracted from $alu->id() to one of the
469product MIBs.
470
471Removes 'tmnxModel' from the name for readability.
472
473=item $alu->fan()
474
475Return the status of all fans from the F<TIMETRA-CHASSIS-MIB>. Returns
476a string indicating the number of fans 'OK' or identification of any fan without
477a 'Ok' operating status.
478
479=item $alu->ps1_status()
480
481Return the status of the first power supply in each chassis from
482the F<TIMETRA-CHASSIS-MIB>.
483
484=item $alu->ps2_status()
485
486Return the status of the second power supply in each chassis from
487the F<TIMETRA-CHASSIS-MIB>.
488
489=back
490
491=head2 Globals imported from SNMP::Info::Layer3
492
493See documentation in L<SNMP::Info::Layer3/"GLOBALS"> for details.
494
495=head1 TABLE METHODS
496
497These are methods that return tables of information in the form of a reference
498to a hash.
499
500=over
501
502=item $alu->i_duplex()
503
504Returns reference to map of IIDs to current link duplex.
505
506=item $alu->i_duplex_admin()
507
508Returns reference to hash of IIDs to admin duplex setting.
509
510=item $alu->agg_ports()
511
512Returns a HASH reference mapping from slave to master port for each member of
513a port bundle on the device. Keys are ifIndex of the slave ports, Values are
514ifIndex of the corresponding master ports.
515
516=back
517
518=head2 Overrides
519
520=over
521
522=item $alu->interfaces()
523
524Returns C<ifName>, since the default Layer3 C<ifDescr> varies based
525upon the transceiver inserted.
526
527=item $alu->lldp_if()
528
529Returns the mapping to the SNMP Interface Table. Utilizes (C<ifIndex>)
530from the (C<tmnxLldpRemEntry >) index.
531
532=back
533
534=head2 LLDP Remote Table (C<lldpRemTable>) uses (C<TIMETRA-LLDP-MIB::tmnxLldpRemTable>)
535
536=over
537
538=item $alu->lldp_rem_id_type()
539
540(C<tmnxLldpRemChassisIdSubtype>)
541
542=item $alu->lldp_rem_id()
543
544(C<tmnxLldpRemChassisId>)
545
546=item $alu->lldp_rem_pid_type()
547
548(C<tmnxLldpRemPortIdSubtype>)
549
550=item $alu->lldp_rem_pid()
551
552(C<tmnxLldpRemPortId>)
553
554=item $alu->lldp_rem_desc()
555
556(C<tmnxLldpRemPortDesc>)
557
558=item $alu->lldp_rem_sysname()
559
560(C<tmnxLldpRemSysName>)
561
562=item $alu->lldp_rem_sysdesc()
563
564(C<tmnxLldpRemSysDesc>)
565
566=item  $alu->lldp_rem_sys_cap()
567
568(C<tmnxLldpRemSysCapEnabled>)
569
570=back
571
572=head2 Entity Table
573
574=over
575
576=item $alu->e_index()
577
578(C<tmnxHwIndex>)
579
580=item $alu->e_class()
581
582Chassis, Module, Fan, Power Supply ...
583
584(C<tmnxHwClass>)
585
586=item $alu->e_descr()
587
588Human Friendly
589
590(C<tmnxHwName>)
591
592=item $alu->e_fwver()
593
594(C<tmnxHwFirmwareCodeVersion>)
595
596=item $alu->e_fru()
597
598BOOLEAN. Is a Field Replaceable unit?
599
600(C<tmnxHwIsFRU>)
601
602=item $alu->e_model()
603
604Model Name of Entity.
605
606(C<tmnxHwMfgBoardNumber>)
607
608=item $alu->e_name()
609
610More computer friendly name of entity.
611
612(C<tmnxHwName>)
613
614=item $alu->e_parent()
615
6160 if root.
617
618(C<tmnxHwContainedIn>)
619
620=item $alu->e_pos()
621
622The relative position among all entities sharing the same parent.
623
624(C<tmnxHwParentRelPos>)
625
626=item $alu->e_serial()
627
628(C<tmnxHwSerialNumber>)
629
630=item $alu->e_swver()
631
632(C<tmnxHwSoftwareCodeVersion>)
633
634=back
635
636=head2 Table Methods imported from SNMP::Info::Layer3
637
638See documentation in L<SNMP::Info::Layer3/"TABLE METHODS"> for details.
639
640=head1 Data Munging Callback Subroutines
641
642=over
643
644=item $alu->munge_tmnx_state()
645
646Removes 'deviceState' or 'device' from C<TmnxDeviceState> strings.
647
648=item $alu->munge_tmnx_e_class()
649
650Attempts to normalize C<tmnxHwClass> to an C<IANAPhysicalClass>.
651
652=item $alu->munge_tmnx_e_swver()
653
654Extracts the software version from C<tmnxHwSoftwareCodeVersion> string.
655
656=back
657
658=cut
659