###########################################################################
#
# This program is part of Zenoss Core, an open source monitoring platform.
# Copyright (C) 2007, 2009, Zenoss Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published by
# the Free Software Foundation.
#
# For complete information please visit: http://www.zenoss.com/oss/
#
###########################################################################

__doc__ = """InterfaceMap

Gather IP network interface information from SNMP, and 
create DMD interface objects

"""

import re

from Products.ZenUtils.Utils import cleanstring, unsigned
from Products.DataCollector.plugins.CollectorPlugin import SnmpPlugin, GetTableMap

class InterfaceMap(SnmpPlugin):
    """
    Map IP network names and aliases to DMD 'interface' objects
    """
    order = 80
    maptype = "InterfaceMap" 
    compname = "os"
    relname = "interfaces"
    modname = "Products.ZenModel.IpInterface"
    deviceProperties = \
                SnmpPlugin.deviceProperties + ('zInterfaceMapIgnoreNames',
                                               'zInterfaceMapIgnoreTypes')
    
    # Interface related tables likely to be used in all subclasses.
    baseSnmpGetTableMaps = (
        GetTableMap('iftable', '.1.3.6.1.2.1.2.2.1', 
                {'.1': 'ifindex',
                 '.2': 'id',
                 '.3': 'type',
                 '.4': 'mtu',
                 '.5': 'speed',
                 '.6': 'macaddress',
                 '.7': 'adminStatus',
                 '.8': 'operStatus'}
        ),
        # ipAddrTable is the better way to get IP addresses
        GetTableMap('ipAddrTable', '.1.3.6.1.2.1.4.20.1',
                {'.1': 'ipAddress',
                 '.2': 'ifindex',
                 '.3': 'netmask'}
        ),
        # Use the ipNetToMediaTable as a backup to the ipAddrTable
        GetTableMap('ipNetToMediaTable', '.1.3.6.1.2.1.4.22.1',
                {'.1': 'ifindex',
                 '.3': 'ipaddress',
                 '.4': 'iptype'}
        ),
        # attempt to determine if the interface supports duplex mode
        GetTableMap('duplex',  '.1.3.6.1.2.1.10.7.2.1',
               {'.19' : 'duplex'}
        ),
    )

    # Base interface tables, plus ones used locally.
    snmpGetTableMaps = baseSnmpGetTableMaps + (
        # Extended interface information.
        GetTableMap('ifalias', '.1.3.6.1.2.1.31.1.1.1',
                {'.6' : 'ifHCInOctets',
                 '.7' : 'ifHCInUcastPkts',
                 '.15': 'highSpeed',
                 '.18': 'description'}
        ),
    )


    def process(self, device, results, log):
        """
        From SNMP info gathered from the device, convert them
        to interface objects.
        """
        getdata, tabledata = results
        log.info('Modeler %s processing data for device %s', self.name(), device.id)
        log.debug( "%s tabledata = %s" % (device.id,tabledata) )
        rm = self.relMap()
        iptable = tabledata.get("ipAddrTable")
        sourceTable = 'ipAddrTable'
        if not iptable:
            iptable = tabledata.get("ipNetToMediaTable")
            if iptable:
                log.info("Unable to use ipAddrTable -- using ipNetToMediaTable instead")
                sourceTable = 'ipNetToMediaTable'
            else:
                log.warn("Unable to get data for %s from either ipAddrTable or"
                          " ipNetToMediaTable" % device.id)

        iftable = tabledata.get("iftable")
        if iftable is None:
            log.error("Unable to get data for %s for iftable -- skipping model" % device.id)
            return None

        ifalias = tabledata.get("ifalias", {})

        self.prepIfTable(log, iftable, ifalias)

        omtable = {}
        duplex = tabledata.get("duplex", {})
        for key, iface in iftable.items():
            if key in duplex:
                iftable[key]['duplex'] = duplex[key].get('duplex', 0)
            else:
                iftable[key]['duplex'] = 0

        for ip, row in iptable.items():
            #FIXME - not getting ifindex back from HP printer
            if 'ifindex' not in row:
                log.debug( "IP entry for %s is missing ifindex" % ip)
                continue

            ip_parts = ip.split('.')
            # If the ipAddrTable key has five octets, that probably
            # means this is a classless subnet (that is, <256).  Usually,
            # the first 4 octets will be the ipAddress we care about.
            # Regardless, we will be using the ip address in the row
            # later anyway.
            if len(ip_parts) == 5 and sourceTable == 'ipAddrTable':
                ip = '.'.join(ip_parts[:-1])
            # If we are using the ipNetToMediaTable, we use the
            # last 4 octets.
            elif len(ip_parts) == 5 and sourceTable == 'ipNetToMediaTable':
                if row['iptype'] != 1:
                    log.debug("iptype (%s) is not 1 -- skipping" % (
                             row['iptype'] ))
                    continue
                ip = '.'.join(ip_parts[1:])
                log.warn("Can't find netmask -- using /24")
                row['netmask'] = '255.255.255.0'

            strindex = str(row['ifindex'])
            if strindex not in omtable and strindex not in iftable:
                log.warn("Skipping %s as it points to missing ifindex %s",
                            row.get('ipAddress',""), row.get('ifindex',""))
                continue

            if strindex not in omtable:
                om = self.processInt(log, device, iftable[strindex])
                if not om:
                    continue
                rm.append(om)
                omtable[strindex] = om
                del iftable[strindex]
            elif strindex in omtable:
                om = omtable[strindex]
            else:
                log.warn("The IP %s points to missing ifindex %s -- skipping" % (
                         ip, strindex) )
                continue

            if not hasattr(om, 'setIpAddresses'):
                om.setIpAddresses = []
            if 'ipAddress' in row:
                ip = row['ipAddress']
            if 'netmask' in row:
                ip = ip + "/" + str(self.maskToBits(row['netmask'].strip()))

            # Ignore IP addresses with a 0.0.0.0 netmask.
            if ip.endswith("/0"):
                log.warn("Ignoring IP address with 0.0.0.0 netmask: %s", ip)
            else:
                om.setIpAddresses.append(ip)
            #om.ifindex = row.ifindex #FIXME ifindex is not set!

        for iface in iftable.values():
            om = self.processInt(log, device, iface)
            if om: rm.append(om)

        return rm

    def prepIfTable(self, log, iftable, ifalias):
        """
        Add interface alias (Cisco description) to iftable
        Sanity check speed
        """
        for ifidx, data in ifalias.items():
            log.debug( "ifalias %s raw data = %s" % (ifidx,data) )
            if ifidx not in iftable:
                log.debug( "ifidx %s is not in iftable -- skipping" % (
                           ifidx))
                continue

            iftable[ifidx]['description'] = data.get('description', '')

            # handle 10GB interfaces using IF-MIB::ifHighSpeed
            speed = iftable[ifidx].get('speed',0)
            if speed == 4294967295L or speed < 0:
                try:
                    iftable[ifidx]['speed'] = data['highSpeed']*1e6
                except KeyError:
                    pass
            
            # Detect availability of the high-capacity counters
            if data.get('ifHCInOctets', None) is not None and \
                data.get('ifHCInUcastPkts', None) is not None:
                iftable[ifidx]['hcCounters'] = True

        for ifidx, data in iftable.items():
            try:
                data['speed'] = unsigned(data['speed'])
            except KeyError:
                pass

    def processInt(self, log, device, iface):
        """
        Convert each interface into an object map, if possible
        Return None if the zProperties match the name or type
        of this iface.
        """
        om = self.objectMap(iface)
        if not hasattr(om, 'id'):
            log.debug( "Can't find 'id' after self.objectMap(iface)"
                       " -- ignoring this interface" )
            return None

        om.id = cleanstring(om.id) #take off \x00 at end of string
        # Left in interfaceName, but added title for
        # the sake of consistency
        if not om.id: 
            om.id = 'Index_%s' % iface.get('ifindex', "")
        om.interfaceName = om.id
        om.title = om.id
        om.id = self.prepId(om.interfaceName)
        if not om.id:
            log.debug( "prepId(%s) doesn't return an id -- skipping" % (
                        om.interfaceName))
            return None
            
        dontCollectIntNames = getattr(device, 'zInterfaceMapIgnoreNames', None)
        if dontCollectIntNames and re.search(dontCollectIntNames, om.interfaceName):
            log.debug( "Interface %s matched the zInterfaceMapIgnoreNames zprop '%s'" % (
                      om.interfaceName, getattr(device, 'zInterfaceMapIgnoreNames')))
            return None

        om.type = self.ifTypes.get(str(getattr(om, 'type', 1)), "Unknown")
        dontCollectIntTypes = getattr(device, 'zInterfaceMapIgnoreTypes', None)
        if dontCollectIntTypes and re.search(dontCollectIntTypes, om.type):
            log.debug( "Interface %s type %s matched the zInterfaceMapIgnoreTypes zprop '%s'" % (
                      om.interfaceName, om.type, 
                      getattr(device, 'zInterfaceMapIgnoreTypes')))
            return None
        
        # Append _64 to interface type if high-capacity counters are supported
        if hasattr(om, 'hcCounters'):
            om.type += "_64"
            del(om.hcCounters)

        if hasattr(om, 'macaddress'):
            if isinstance(om.macaddress, basestring):
                om.macaddress = self.asmac(om.macaddress)
            else:
                log.debug("The MAC address for interface %s is invalid (%s)" \
                         " -- ignoring", om.id, om.macaddress)

        # Handle misreported operStatus from Linux tun devices
        if om.id.startswith('tun') and om.adminStatus == 1: om.operStatus = 1
        
        # Net-SNMP on Linux will always report the speed of bond interfaces as
        # 10Mbps. This is probably due to the complexity of figuring out the
        # real speed which would take into account all bonded interfaces and
        # the bonding method (aggregate/failover). The problem is that 10Mbps
        # is a really bad default. The following check changes this default to
        # 1Gbps instead. See the following article that explains how you can
        # manually set this per-interface in the device's snmpd.conf for better
        # accuracy.
        #
        # http://whocares.de/2007/12/28/speed-up-your-bonds/
        if om.id.startswith('bond') and om.speed == 1e7:
            newspeed = 1000000000
            log.debug( "Resetting bond interface %s speed from %s to %s" % (
                      om.interfaceName, om.speed, newspeed))
            om.speed = newspeed
        
        # Clear out all IP addresses for interfaces that no longer have any.
        if not hasattr(om, 'setIpAddresses'):
            om.setIpAddresses = []
        
        return om
   

    ifTypes = {'1': 'Other',
     '2': 'regular1822',
     '3': 'hdh1822',
     '4': 'ddnX25',
     '5': 'rfc877x25',
     '6': 'ethernetCsmacd',
     '7': 'iso88023Csmacd',
     '8': 'iso88024TokenBus',
     '9': 'iso88025TokenRing',
     '10': 'iso88026Man',
     '11': 'starLan',
     '12': 'proteon10Mbit',
     '13': 'proteon80Mbit',
     '14': 'hyperchannel',
     '15': 'fddi',
     '16': 'lapb',
     '17': 'sdlc',
     '18': 'ds1',
     '19': 'e1',
     '20': 'basicISDN',
     '21': 'primaryISDN',
     '22': 'propPointToPointSerial',
     '23': 'ppp',
     '24': 'softwareLoopback',
     '25': 'eon',
     '26': 'ethernet-3Mbit',
     '27': 'nsip',
     '28': 'slip',
     '29': 'ultra',
     '30': 'ds3',
     '31': 'sip',
     '32': 'frame-relay',
     '33': 'rs232',
     '34': 'para',
     '35': 'arcnet',
     '36': 'arcnetPlus',
     '37': 'atm',
     '38': 'miox25',
     '39': 'sonet',
     '40': 'x25ple',
     '41': 'iso88022llc',
     '42': 'localTalk',
     '43': 'smdsDxi',
     '44': 'frameRelayService',
     '45': 'v35',
     '46': 'hssi',
     '47': 'hippi',
     '48': 'modem',
     '49': 'aal5',
     '50': 'sonetPath',
     '51': 'sonetVT',
     '52': 'smdsIcip',
     '53': 'propVirtual',
     '54': 'propMultiplexor',
     '55': '100BaseVG',
     '56': 'Fibre Channel',
     '57': 'HIPPI Interface',
     '58': 'Obsolete for FrameRelay',
     '59': 'ATM Emulation of 802.3 LAN',
     '60': 'ATM Emulation of 802.5 LAN',
     '61': 'ATM Emulation of a Circuit',
     '62': 'FastEthernet (100BaseT)',
     '63': 'ISDN',
     '64': 'CCITT V.11_X.21',
     '65': 'CCITT V.36',
     '66': 'CCITT G703 at 64Kbps',
     '67': 'Obsolete G702 see DS1-MIB',
     '68': 'SNA QLLC',
     '69': 'Full Duplex Fast Ethernet (100BaseFX)',
     '70': 'Channel',
     '71': 'Radio Spread Spectrum (802.11)',
     '72': 'IBM System 360_370 OEMI Channel',
     '73': 'IBM Enterprise Systems Connection',
     '74': 'Data Link Switching',
     '75': 'ISDN S_T Interface',
     '76': 'ISDN U Interface',
     '77': 'Link Access Protocol D (LAPD)',
     '78': 'IP Switching Opjects',
     '79': 'Remote Source Route Bridging',
     '80': 'ATM Logical Port',
     '81': 'ATT DS0 Point (64 Kbps)',
     '82': 'ATT Group of DS0 on a single DS1',
     '83': 'BiSync Protocol (BSC)',
     '84': 'Asynchronous Protocol',
     '85': 'Combat Net Radio',
     '86': 'ISO 802.5r DTR',
     '87': 'Ext Pos Loc Report Sys',
     '88': 'Apple Talk Remote Access Protocol',
     '89': 'Proprietary Connectionless Protocol',
     '90': 'CCITT-ITU X.29 PAD Protocol',
     '91': 'CCITT-ITU X.3 PAD Facility',
     '92': 'MultiProtocol Connection over Frame_Relay',
     '93': 'CCITT-ITU X213',
     '94': 'Asymetric Digitial Subscriber Loop (ADSL)',
     '95': 'Rate-Adapt Digital Subscriber Loop (RDSL)',
     '96': 'Symetric Digitial Subscriber Loop (SDSL)',
     '97': 'Very High Speed Digitial Subscriber Loop (HDSL)',
     '98': 'ISO 802.5 CRFP',
     '99': 'Myricom Myrinet',
     '100': 'Voice recEive and transMit (voiceEM)',
     '101': 'Voice Foreign eXchange Office (voiceFXO)',
     '102': 'Voice Foreign eXchange Station (voiceFXS)',
     '103': 'Voice Encapulation',
     '104': 'Voice Over IP Encapulation',
     '105': 'ATM DXI',
     '106': 'ATM FUNI',
     '107': 'ATM IMA',
     '108': 'PPP Multilink Bundle',
     '109': 'IBM IP over CDLC',
     '110': 'IBM Common Link Access to Workstation',
     '111': 'IBM Stack to Stack',
     '112': 'IBM Virtual IP Address (VIPA)',
     '113': 'IBM Multi-Protocol Channel Support',
     '114': 'IBM IP over ATM',
     '115': 'ISO 802.5j Fiber Token Ring',
     '116': 'IBM Twinaxial Data Link Control (TDLC)',
     '117': 'Gigabit Ethernet',
     '118': 'Higher Data Link Control (HDLC)',
     '119': 'Link Access Protocol F (LAPF)',
     '120': 'CCITT V.37',
     '121': 'CCITT X.25 Multi-Link Protocol',
     '122': 'CCITT X.25 Hunt Group',
     '123': 'Transp HDLC',
     '124': 'Interleave Channel',
     '125': 'Fast Channel',
     '126': 'IP (for APPN HPR in IP Networks)',
     '127': 'CATV MAC Layer',
     '128': 'CATV Downstream Interface',
     '129': 'CATV Upstream Interface',
     '130': 'Avalon Parallel Processor',
     '131': 'Encapsulation Interface',
     '132': 'Coffee Pot',
     '133': 'Circuit Emulation Service',
     '134': 'ATM Sub Interface',
     '135': 'Layer 2 Virtual LAN using 802.1Q',
     '136': 'Layer 3 Virtual LAN using IP',
     '137': 'Layer 3 Virtual LAN using IPX',
     '138': 'IP Over Power Lines',
     '139': 'Multi-Media Mail over IP',
     '140': 'Dynamic synchronous Transfer Mode (DTM)',
     '141': 'Data Communications Network',
     '142': 'IP Forwarding Interface',
     '143': 'Multi-rate Symmetric DSL',
     '144': 'IEEE1394 High Performance Serial Bus',
     '145': 'HIPPI-6400',
     '146': 'DVB-RCC MAC Layer',
     '147': 'DVB-RCC Downstream Channel',
     '148': 'DVB-RCC Upstream Channel',
     '149': 'ATM Virtual Interface',
     '150': 'MPLS Tunnel Virtual Interface',
     '151': 'Spatial Reuse Protocol',
     '152': 'Voice Over ATM',
     '153': 'Voice Over Frame Relay',
     '154': 'Digital Subscriber Loop over ISDN',
     '155': 'Avici Composite Link Interface',
     '156': 'SS7 Signaling Link',
     '157': 'Prop. P2P wireless interface',
     '158': 'Frame Forward Interface',
     '159': 'Multiprotocol over ATM AAL5',
     '160': 'USB Interface',
     '161': 'IEEE 802.3ad Link Aggregate',
     '162': 'BGP Policy Accounting',
     '163': 'FRF .16 Multilink Frame Relay',
     '164': 'H323 Gatekeeper',
     '165': 'H323 Voice and Video Proxy',
     '166': 'MPLS',
     '167': 'Multi-frequency signaling link',
     '168': 'High Bit-Rate DSL - 2nd generation',
     '169': 'Multirate HDSL2',
     '170': 'Facility Data Link 4Kbps on a DS1',
     '171': 'Packet over SONET_SDH Interface',
     '172': 'DVB-ASI Input',
     '173': 'DVB-ASI Output',
     '174': 'Power Line Communtications',
     '175': 'Non Facility Associated Signaling',
     '176': 'TR008',
     '177': 'Remote Digital Terminal',
     '178': 'Integrated Digital Terminal',
     '179': 'ISUP',
     '180': 'prop_Maclayer',
     '181': 'prop_Downstream',
     '182': 'prop_Upstream',
     '183': 'HIPERLAN Type 2 Radio Interface',
     '184': 'PropBroadbandWirelessAccesspt2multipt',
     '185': 'SONET Overhead Channel',
     '186': 'Digital Wrapper',
     '187': 'ATM adaptation layer 2',
     '188': 'MAC layer over radio links',
     '189': 'ATM over radio links',
     '190': 'Inter Machine Trunks',
     '191': 'Multiple Virtual Lines DSL',
     '192': 'Long Rach DSL',
     '193': 'Frame Relay DLCI End Point',
     '194': 'ATM VCI End Point',
     '195': 'Optical Channel',
     '196': 'Optical Transport',
     '197': 'Proprietary ATM',
     '198': 'Voice Over Cable Interface',
     '199': 'Infiniband',
     '200': 'TE Link',
     '201': 'Q.2931',
     '202': 'Virtual Trunk Group',
     '203': 'SIP Trunk Group',
     '204': 'SIP Signaling',
     '205': 'CATV Upstream Channel',
     '206': 'Acorn Econet',
     '207': 'FSAN 155Mb Symetrical PON interface',
     '208': 'FSAN622Mb Symetrical PON interface',
     '209': 'Transparent bridge interface',
     '210': 'Interface common to multiple lines',
     '211': 'voice E and M Feature Group D',
     '212': 'voice FGD Exchange Access North American',
     '213': 'voice Direct Inward Dialing',
     '214': 'MPEG transport interface',
     '215': '6to4 interface (DEPRECATED)',
     '216': 'GTP (GPRS Tunneling Protocol)',
     '217': 'Paradyne EtherLoop 1',
     '218': 'Paradyne EtherLoop 2',
     '219': 'Optical Channel Group',
     '220': 'HomePNA ITU-T G.989',
     '221': 'Generic Framing Procedure (GFP)',
     '222': 'Layer 2 Virtual LAN using Cisco ISL',
     '223': 'Acteleis proprietary MetaLOOP High Speed Link',
     '224': 'FCIP Link',
     '225': 'Resilient Packet Ring Interface Type',
     '226': 'RF Qam Interface',
     '227': 'Link Management Protocol',
     '228': 'Cambridge Broadband Limited VectaStar',
     '229': 'CATV Modular CMTS Downstream Interface',
     '230': 'Asymmetric Digital Subscriber Loop Version 2',
     '231': 'MACSecControlled',
     '232': 'MACSecUncontrolled', 
     }
