Changeset 2877

Show
Ignore:
Timestamp:
05/03/06 19:19:53 (18 years ago)
Author:
jerome
Message:

Added support for pysnmp v4.x in addition to v3.4.x

Location:
pykota/trunk
Files:
5 modified

Legend:

Unmodified
Added
Removed
  • pykota/trunk/bin/pkturnkey

    r2831 r2877  
    221221    def supportsSNMP(self, hostname, community) : 
    222222        """Returns 1 if the printer accepts SNMP queries, else 0.""" 
     223        pageCounterOID = "1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1 
    223224        try : 
    224             from pysnmp.asn1.encoding.ber.error import TypeMismatchError 
    225             from pysnmp.mapping.udp.role import Manager 
    226             from pysnmp.proto.api import alpha 
     225            from pysnmp.entity.rfc3413.oneliner import cmdgen 
    227226        except ImportError :     
    228             sys.stderr.write("pysnmp doesn't seem to be installed. SNMP checks will be ignored !\n") 
    229             return 0 
    230              
    231         pageCounterOID = ".1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1 
    232         def retrieveSNMPValues(hostname, community) :     
    233             """Retrieves a printer's internal page counter and status via SNMP.""" 
    234             ver = alpha.protoVersions[alpha.protoVersionId1] 
    235             req = ver.Message() 
    236             req.apiAlphaSetCommunity(community) 
    237             req.apiAlphaSetPdu(ver.GetRequestPdu()) 
    238             req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null())) 
    239             tsp = Manager() 
     227            hasV4 = False 
    240228            try : 
    241                 tsp.sendAndReceive(req.berEncode(), \ 
    242                                    (hostname, 161), \ 
    243                                    (handleAnswer, req)) 
    244             except :     
    245                 raise "No SNMP !" 
    246             tsp.close() 
    247          
    248         def handleAnswer(wholemsg, notusedhere, req): 
    249             """Decodes and handles the SNMP answer.""" 
    250             ver = alpha.protoVersions[alpha.protoVersionId1] 
    251             rsp = ver.Message() 
    252             try : 
    253                 rsp.berDecode(wholemsg) 
    254             except TypeMismatchError, msg :     
    255                 raise "No SNMP !" 
    256             else : 
    257                 if req.apiAlphaMatch(rsp): 
    258                     errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus() 
    259                     if errorStatus: 
    260                         raise "No SNMP !" 
    261                     else: 
    262                         self.values = [] 
    263                         for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList(): 
    264                             self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value) 
    265                         try :     
    266                             pagecounter = self.values[0] 
    267                         except : 
     229                from pysnmp.asn1.encoding.ber.error import TypeMismatchError 
     230                from pysnmp.mapping.udp.role import Manager 
     231                from pysnmp.proto.api import alpha 
     232            except ImportError :     
     233                sys.stderr.write("pysnmp doesn't seem to be installed. SNMP checks will be ignored !\n") 
     234                return 0 
     235        else :         
     236            hasV4 = True 
     237             
     238        if hasV4 :     
     239            def retrieveSNMPValues(hostname, community) : 
     240                """Retrieves a printer's internal page counter and status via SNMP.""" 
     241                errorIndication, errorStatus, errorIndex, varBinds = \ 
     242                     cmdgen.CommandGenerator().getCmd(cmdgen.CommunityData("pykota", community, 0), \ 
     243                                                      cmdgen.UdpTransportTarget((hostname, 161)), \ 
     244                                                      tuple([int(i) for i in pageCounterOID.split('.')])) 
     245                if errorIndication :                                                   
     246                    raise "No SNMP !" 
     247                elif errorStatus :     
     248                    raise "No SNMP !" 
     249                else :                                  
     250                    self.SNMPOK = True 
     251        else : 
     252            def retrieveSNMPValues(hostname, community) :     
     253                """Retrieves a printer's internal page counter and status via SNMP.""" 
     254                ver = alpha.protoVersions[alpha.protoVersionId1] 
     255                req = ver.Message() 
     256                req.apiAlphaSetCommunity(community) 
     257                req.apiAlphaSetPdu(ver.GetRequestPdu()) 
     258                req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null())) 
     259                tsp = Manager() 
     260                try : 
     261                    tsp.sendAndReceive(req.berEncode(), \ 
     262                                       (hostname, 161), \ 
     263                                       (handleAnswer, req)) 
     264                except :     
     265                    raise "No SNMP !" 
     266                tsp.close() 
     267             
     268            def handleAnswer(wholemsg, notusedhere, req): 
     269                """Decodes and handles the SNMP answer.""" 
     270                ver = alpha.protoVersions[alpha.protoVersionId1] 
     271                rsp = ver.Message() 
     272                try : 
     273                    rsp.berDecode(wholemsg) 
     274                except TypeMismatchError, msg :     
     275                    raise "No SNMP !" 
     276                else : 
     277                    if req.apiAlphaMatch(rsp): 
     278                        errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus() 
     279                        if errorStatus: 
    268280                            raise "No SNMP !" 
    269                         else :     
    270                             self.SNMPOK = 1 
    271                             return 1 
     281                        else: 
     282                            self.values = [] 
     283                            for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList(): 
     284                                self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value) 
     285                            try :     
     286                                pagecounter = self.values[0] 
     287                            except : 
     288                                raise "No SNMP !" 
     289                            else :     
     290                                self.SNMPOK = 1 
     291                                return 1 
    272292             
    273293        self.SNMPOK = 0 
  • pykota/trunk/NEWS

    r2868 r2877  
    2222PyKota NEWS : 
    2323        
     24    - 1.25alpha2 (2006-05-03) : 
     25     
     26        - Added support for PySNMP v4.x in addition to 3.4.x. 
     27         
    2428    - 1.25alpha1 (2006-04-14) : 
    2529     
  • pykota/trunk/pykota/accounters/snmp.py

    r2830 r2877  
    3333 
    3434try : 
    35     from pysnmp.asn1.encoding.ber.error import TypeMismatchError 
    36     from pysnmp.mapping.udp.error import SnmpOverUdpError 
    37     from pysnmp.mapping.udp.role import Manager 
    38     from pysnmp.proto.api import alpha 
    39 except ImportError : 
    40     class Handler : 
    41         def __init__(self, parent, printerhostname) : 
    42             """Just there to raise an exception.""" 
    43             raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/" 
    44 else :     
    45     pageCounterOID = ".1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1 
    46     hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1 
    47     printerStatusValues = { 1 : 'other', 
    48                             2 : 'unknown', 
    49                             3 : 'idle', 
    50                             4 : 'printing', 
    51                             5 : 'warmup', 
    52                           } 
    53     hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1 
    54     deviceStatusValues = { 1 : 'unknown', 
    55                            2 : 'running', 
    56                            3 : 'warning', 
    57                            4 : 'testing', 
    58                            5 : 'down', 
    59                          }   
    60     hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 
    61     prtConsoleDisplayBufferTextOID = ".1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1 
    62                            
    63     #                       
    64     # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB) 
    65     # 
    66     class Handler : 
    67         """A class for SNMP print accounting.""" 
    68         def __init__(self, parent, printerhostname) : 
    69             self.parent = parent 
    70             self.printerHostname = printerhostname 
    71             try : 
    72                 self.community = self.parent.arguments.split(":")[1].strip() 
    73             except IndexError :     
    74                 self.community = "public" 
    75             self.port = 161 
    76             self.printerInternalPageCounter = None 
    77             self.printerStatus = None 
    78             self.deviceStatus = None 
    79              
     35    from pysnmp.entity.rfc3413.oneliner import cmdgen 
     36except ImportError :     
     37    hasV4 = False 
     38    try : 
     39        from pysnmp.asn1.encoding.ber.error import TypeMismatchError 
     40        from pysnmp.mapping.udp.error import SnmpOverUdpError 
     41        from pysnmp.mapping.udp.role import Manager 
     42        from pysnmp.proto.api import alpha 
     43    except ImportError : 
     44        raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/" 
     45else : 
     46    hasV4 = True 
     47 
     48#                       
     49# Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB) 
     50# 
     51pageCounterOID = "1.3.6.1.2.1.43.10.2.1.4.1.1"  # SNMPv2-SMI::mib-2.43.10.2.1.4.1.1 
     52hrPrinterStatusOID = "1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1 
     53printerStatusValues = { 1 : 'other', 
     54                        2 : 'unknown', 
     55                        3 : 'idle', 
     56                        4 : 'printing', 
     57                        5 : 'warmup', 
     58                      } 
     59hrDeviceStatusOID = "1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1 
     60deviceStatusValues = { 1 : 'unknown', 
     61                       2 : 'running', 
     62                       3 : 'warning', 
     63                       4 : 'testing', 
     64                       5 : 'down', 
     65                     }   
     66hrPrinterDetectedErrorStateOID = "1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1 
     67prtConsoleDisplayBufferTextOID = "1.3.6.1.2.1.43.16.5.1.2.1.1" # SNMPv2-SMI::mib-2.43.16.5.1.2.1.1 
     68class BaseHandler : 
     69    """A class for SNMP print accounting.""" 
     70    def __init__(self, parent, printerhostname) : 
     71        self.parent = parent 
     72        self.printerHostname = printerhostname 
     73        try : 
     74            self.community = self.parent.arguments.split(":")[1].strip() 
     75        except IndexError :     
     76            self.community = "public" 
     77        self.port = 161 
     78        self.printerInternalPageCounter = None 
     79        self.printerStatus = None 
     80        self.deviceStatus = None 
     81         
     82    def retrieveSNMPValues(self) :     
     83        """Retrieves a printer's internal page counter and status via SNMP.""" 
     84        raise RuntimeError, "You have to overload this method." 
     85         
     86    def waitPrinting(self) : 
     87        """Waits for printer status being 'printing'.""" 
     88        previousValue = self.parent.getLastPageCounter() 
     89        timebefore = time.time() 
     90        firstvalue = None 
     91        while 1: 
     92            self.retrieveSNMPValues() 
     93            statusAsString = printerStatusValues.get(self.printerStatus) 
     94            if statusAsString in ('printing', 'warmup') : 
     95                break 
     96            if self.printerInternalPageCounter is not None :     
     97                if firstvalue is None : 
     98                    # first time we retrieved a page counter, save it 
     99                    firstvalue = self.printerInternalPageCounter 
     100                else :      
     101                    # second time (or later) 
     102                    if firstvalue < self.printerInternalPageCounter : 
     103                        # Here we have a printer which lies : 
     104                        # it says it is not printing or warming up 
     105                        # BUT the page counter increases !!! 
     106                        # So we can probably quit being sure it is printing. 
     107                        self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn") 
     108                        break 
     109                    elif (time.time() - timebefore) > NOPRINTINGMAXDELAY : 
     110                        # More than X seconds without the printer being in 'printing' mode 
     111                        # We can safely assume this won't change if printer is now 'idle' 
     112                        pstatusAsString = printerStatusValues.get(self.printerStatus) 
     113                        dstatusAsString = deviceStatusValues.get(self.deviceStatus) 
     114                        if (pstatusAsString == 'idle') or \ 
     115                            ((pstatusAsString == 'other') and \ 
     116                             (dstatusAsString == 'running')) : 
     117                            if self.printerInternalPageCounter == previousValue : 
     118                                # Here the job won't be printed, because probably 
     119                                # the printer rejected it for some reason. 
     120                                self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn") 
     121                            else :      
     122                                # Here the job has already been entirely printed, and 
     123                                # the printer has already passed from 'idle' to 'printing' to 'idle' again. 
     124                                self.parent.filter.printInfo("Printer %s has probably already printed this job !!!" % self.parent.filter.PrinterName, "warn") 
     125                            break 
     126            self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName)     
     127            time.sleep(ITERATIONDELAY) 
     128         
     129    def waitIdle(self) : 
     130        """Waits for printer status being 'idle'.""" 
     131        idle_num = idle_flag = 0 
     132        while 1 : 
     133            self.retrieveSNMPValues() 
     134            pstatusAsString = printerStatusValues.get(self.printerStatus) 
     135            dstatusAsString = deviceStatusValues.get(self.deviceStatus) 
     136            idle_flag = 0 
     137            if (pstatusAsString == 'idle') or \ 
     138               ((pstatusAsString == 'other') and \ 
     139                (dstatusAsString == 'running')) : 
     140                idle_flag = 1       # Standby / Powersave is considered idle 
     141            if idle_flag :     
     142                idle_num += 1 
     143                if idle_num >= STABILIZATIONDELAY : 
     144                    # printer status is stable, we can exit 
     145                    break 
     146            else :     
     147                idle_num = 0 
     148            self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName)     
     149            time.sleep(ITERATIONDELAY) 
     150             
     151    def retrieveInternalPageCounter(self) : 
     152        """Returns the page counter from the printer via internal SNMP handling.""" 
     153        try : 
     154            if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ 
     155               (os.environ.get("PYKOTAACTION") == "ALLOW") and \ 
     156               (os.environ.get("PYKOTAPHASE") == "AFTER") and \ 
     157               self.parent.filter.JobSizeBytes : 
     158                self.waitPrinting() 
     159            self.waitIdle()     
     160        except :     
     161            self.parent.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.PrinterName), "warn") 
     162            raise 
     163        return self.printerInternalPageCounter 
     164             
     165if hasV4 :             
     166    class Handler(BaseHandler) : 
     167        """A class for pysnmp v4.x""" 
     168        def retrieveSNMPValues(self) : 
     169            """Retrieves a printer's internal page counter and status via SNMP.""" 
     170            errorIndication, errorStatus, errorIndex, varBinds = \ 
     171                 cmdgen.CommandGenerator().getCmd(cmdgen.CommunityData("pykota", self.community, 0), \ 
     172                                                  cmdgen.UdpTransportTarget((self.printerHostname, self.port)), \ 
     173                                                  tuple([int(i) for i in pageCounterOID.split('.')]), \ 
     174                                                  tuple([int(i) for i in hrPrinterStatusOID.split('.')]), \ 
     175                                                  tuple([int(i) for i in hrDeviceStatusOID.split('.')])) 
     176            if errorIndication :                                                   
     177                self.parent.filter.printInfo("SNMP Error : %s" % errorIndication, "error") 
     178            elif errorStatus :     
     179                self.parent.filter.printInfo("SNMP Error : %s at %s" % (errorStatus.prettyPrint(), \ 
     180                                                                        varBinds[int(errorIndex)-1]), \ 
     181                                             "error") 
     182            else :                                  
     183                self.printerInternalPageCounter = max(self.printerInternalPageCounter, int(varBinds[0][1].prettyPrint())) 
     184                self.printerStatus = int(varBinds[1][1].prettyPrint()) 
     185                self.deviceStatus = int(varBinds[2][1].prettyPrint()) 
     186                self.parent.filter.logdebug("SNMP answer decoded : PageCounter : %s  PrinterStatus : '%s'  DeviceStatus : '%s'" \ 
     187                     % (self.printerInternalPageCounter, \ 
     188                        printerStatusValues.get(self.printerStatus), \ 
     189                        deviceStatusValues.get(self.deviceStatus))) 
     190else : 
     191    class Handler(BaseHandler) : 
     192        """A class for pysnmp v3.4.x""" 
    80193        def retrieveSNMPValues(self) :     
    81194            """Retrieves a printer's internal page counter and status via SNMP.""" 
     
    95208                self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn") 
    96209            tsp.close() 
    97      
     210         
    98211        def handleAnswer(self, wholeMsg, notusedhere, req): 
    99212            """Decodes and handles the SNMP answer.""" 
     
    128241                        else :     
    129242                            return 1 
    130                          
    131         def waitPrinting(self) : 
    132             """Waits for printer status being 'printing'.""" 
    133             previousValue = self.parent.getLastPageCounter() 
    134             timebefore = time.time() 
    135             firstvalue = None 
    136             while 1: 
    137                 self.retrieveSNMPValues() 
    138                 statusAsString = printerStatusValues.get(self.printerStatus) 
    139                 if statusAsString in ('printing', 'warmup') : 
    140                     break 
    141                 if self.printerInternalPageCounter is not None :     
    142                     if firstvalue is None : 
    143                         # first time we retrieved a page counter, save it 
    144                         firstvalue = self.printerInternalPageCounter 
    145                     else :      
    146                         # second time (or later) 
    147                         if firstvalue < self.printerInternalPageCounter : 
    148                             # Here we have a printer which lies : 
    149                             # it says it is not printing or warming up 
    150                             # BUT the page counter increases !!! 
    151                             # So we can probably quit being sure it is printing. 
    152                             self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.PrinterName, "warn") 
    153                             break 
    154                         elif (time.time() - timebefore) > NOPRINTINGMAXDELAY : 
    155                             # More than X seconds without the printer being in 'printing' mode 
    156                             # We can safely assume this won't change if printer is now 'idle' 
    157                             pstatusAsString = printerStatusValues.get(self.printerStatus) 
    158                             dstatusAsString = deviceStatusValues.get(self.deviceStatus) 
    159                             if (pstatusAsString == 'idle') or \ 
    160                                 ((pstatusAsString == 'other') and \ 
    161                                  (dstatusAsString == 'running')) : 
    162                                 if self.printerInternalPageCounter == previousValue : 
    163                                     # Here the job won't be printed, because probably 
    164                                     # the printer rejected it for some reason. 
    165                                     self.parent.filter.printInfo("Printer %s probably won't print this job !!!" % self.parent.filter.PrinterName, "warn") 
    166                                 else :      
    167                                     # Here the job has already been entirely printed, and 
    168                                     # the printer has already passed from 'idle' to 'printing' to 'idle' again. 
    169                                     self.parent.filter.printInfo("Printer %s has probably already printed this job !!!" % self.parent.filter.PrinterName, "warn") 
    170                                 break 
    171                 self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.PrinterName)     
    172                 time.sleep(ITERATIONDELAY) 
    173              
    174         def waitIdle(self) : 
    175             """Waits for printer status being 'idle'.""" 
    176             idle_num = idle_flag = 0 
    177             while 1 : 
    178                 self.retrieveSNMPValues() 
    179                 pstatusAsString = printerStatusValues.get(self.printerStatus) 
    180                 dstatusAsString = deviceStatusValues.get(self.deviceStatus) 
    181                 idle_flag = 0 
    182                 if (pstatusAsString == 'idle') or \ 
    183                    ((pstatusAsString == 'other') and \ 
    184                     (dstatusAsString == 'running')) : 
    185                     idle_flag = 1       # Standby / Powersave is considered idle 
    186                 if idle_flag :     
    187                     idle_num += 1 
    188                     if idle_num >= STABILIZATIONDELAY : 
    189                         # printer status is stable, we can exit 
    190                         break 
    191                 else :     
    192                     idle_num = 0 
    193                 self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.PrinterName)     
    194                 time.sleep(ITERATIONDELAY) 
    195                  
    196         def retrieveInternalPageCounter(self) : 
    197             """Returns the page counter from the printer via internal SNMP handling.""" 
    198             try : 
    199                 if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \ 
    200                    (os.environ.get("PYKOTAACTION") == "ALLOW") and \ 
    201                    (os.environ.get("PYKOTAPHASE") == "AFTER") and \ 
    202                    self.parent.filter.JobSizeBytes : 
    203                     self.waitPrinting() 
    204                 self.waitIdle()     
    205             except :     
    206                 self.parent.filter.printInfo(_("SNMP querying stage interrupted. Using latest value seen for internal page counter (%s) on printer %s.") % (self.printerInternalPageCounter, self.parent.filter.PrinterName), "warn") 
    207                 raise 
    208             return self.printerInternalPageCounter 
    209              
     243                     
    210244def main(hostname) : 
    211245    """Tries SNMP accounting for a printer host.""" 
  • pykota/trunk/pykota/version.py

    r2858 r2877  
    2222# 
    2323 
    24 __version__ = "1.25alpha1_unofficial" 
     24__version__ = "1.25alpha2_unofficial" 
    2525 
    2626__doc__ = "PyKota : a complete Printing Quota Solution for CUPS." 
  • pykota/trunk/README

    r2870 r2877  
    375375    - The Python-SNMP module to query printers for their page counter. 
    376376      (http://pysnmp.sourceforge.net).  
    377       IMPORTANT : version 3.4.4 (or 3.4.3 or 3.4.2) is REQUIRED. 
    378       Versions 2.x or 4.x won't work for now. 
     377      IMPORTANT : version 3.4.2 or higher is REQUIRED. 
     378      Versions 2.x won't work. Versions 4.x now work (tested with v4.1.5a). 
    379379    - The Python-OSD module to use the graphical print quota reminder. 
    380380      (http://repose.cx/pyosd/)