root / pykota / trunk / pykota / accounters / snmp.py @ 2205

Revision 2205, 10.4 kB (checked in by jerome, 19 years ago)

Split hardware.py into a generic hardware.py and two protocol handler
submodules.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Auth Date Rev Id
Line 
1# PyKota
2# -*- coding: ISO-8859-15 -*-
3#
4# PyKota - Print Quotas for CUPS and LPRng
5#
6# (c) 2003, 2004, 2005 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23#
24
25ITERATIONDELAY = 1.0   # 1 Second
26STABILIZATIONDELAY = 3 # We must read three times the same value to consider it to be stable
27
28import sys
29import os
30import time
31
32try :
33    from pysnmp.asn1.encoding.ber.error import TypeMismatchError
34    from pysnmp.mapping.udp.error import SnmpOverUdpError
35    from pysnmp.mapping.udp.role import Manager
36    from pysnmp.proto.api import alpha
37except ImportError :
38    class Handler :
39        def __init__(self, parent, printerhostname) :
40            """Just there to raise an exception."""
41            raise RuntimeError, "The pysnmp module is not available. Download it from http://pysnmp.sf.net/"
42else :   
43    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
44    pageCounterOID2 = ".1.3.6.1.2.1.43.10.2.1.5.1.1" # SNMPv2-SMI::mib-2.43.10.2.1.5.1.1
45    hrPrinterStatusOID = ".1.3.6.1.2.1.25.3.5.1.1.1" # SNMPv2-SMI::mib-2.25.3.5.1.1.1
46    printerStatusValues = { 1 : 'other',
47                            2 : 'unknown',
48                            3 : 'idle',
49                            4 : 'printing',
50                            5 : 'warmup',
51                          }
52    hrDeviceStatusOID = ".1.3.6.1.2.1.25.3.2.1.5.1" # SNMPv2-SMI::mib-2.25.3.2.1.5.1
53    deviceStatusValues = { 1 : 'unknown',
54                           2 : 'running',
55                           3 : 'warning',
56                           4 : 'testing',
57                           5 : 'down',
58                         } 
59    hrPrinterDetectedErrorStateOID = ".1.3.6.1.2.1.25.3.5.1.2.1" # SNMPv2-SMI::mib-2.25.3.5.1.2.1
60    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
61                         
62    #                     
63    # Documentation taken from RFC 3805 (Printer MIB v2) and RFC 2790 (Host Resource MIB)
64    #
65    class Handler :
66        """A class for SNMP print accounting."""
67        def __init__(self, parent, printerhostname) :
68            self.parent = parent
69            self.printerHostname = printerhostname
70            self.printerInternalPageCounter = None
71            self.printerInternalPageCounter2 = None
72            self.printerStatus = None
73            self.deviceStatus = None
74           
75        def retrieveSNMPValues(self) :   
76            """Retrieves a printer's internal page counter and status via SNMP."""
77            ver = alpha.protoVersions[alpha.protoVersionId1]
78            req = ver.Message()
79            req.apiAlphaSetCommunity('public')
80            req.apiAlphaSetPdu(ver.GetRequestPdu())
81            req.apiAlphaGetPdu().apiAlphaSetVarBindList((pageCounterOID, ver.Null()), \
82                                                        (pageCounterOID2, ver.Null()), \
83                                                        (hrPrinterStatusOID, ver.Null()), \
84                                                        (hrDeviceStatusOID, ver.Null()))
85            tsp = Manager()
86            try :
87                tsp.sendAndReceive(req.berEncode(), (self.printerHostname, 161), (self.handleAnswer, req))
88            except SnmpOverUdpError, msg :   
89                self.parent.filter.printInfo(_("Network error while doing SNMP queries on printer %s : %s") % (self.printerHostname, msg), "warn")
90            tsp.close()
91   
92        def handleAnswer(self, wholeMsg, notusedhere, req):
93            """Decodes and handles the SNMP answer."""
94            self.parent.filter.logdebug("SNMP message : '%s'" % repr(wholeMsg))
95            ver = alpha.protoVersions[alpha.protoVersionId1]
96            rsp = ver.Message()
97            try :
98                rsp.berDecode(wholeMsg)
99            except TypeMismatchError, msg :   
100                self.parent.filter.printInfo(_("SNMP message decoding error for printer %s : %s") % (self.printerHostname, msg), "warn")
101            else :
102                if req.apiAlphaMatch(rsp):
103                    errorStatus = rsp.apiAlphaGetPdu().apiAlphaGetErrorStatus()
104                    if errorStatus:
105                        self.parent.filter.printInfo(_("Problem encountered while doing SNMP queries on printer %s : %s") % (self.printerHostname, errorStatus), "warn")
106                    else:
107                        self.values = []
108                        for varBind in rsp.apiAlphaGetPdu().apiAlphaGetVarBindList():
109                            self.values.append(varBind.apiAlphaGetOidVal()[1].rawAsn1Value)
110                        try :   
111                            # keep maximum value seen for printer's internal page counter
112                            self.printerInternalPageCounter = max(self.printerInternalPageCounter, self.values[0])
113                            self.printerInternalPageCounter2 = max(self.printerInternalPageCounter2, self.values[1])
114                            self.printerStatus = self.values[2]
115                            self.deviceStatus = self.values[3]
116                            self.parent.filter.logdebug("SNMP answer is decoded : PageCounters : (%s, %s)  PrinterStatus : %s  DeviceStatus : %s" % tuple(self.values))
117                        except IndexError :   
118                            self.parent.filter.logdebug("SNMP answer is incomplete : %s" % str(self.values))
119                            pass
120                        else :   
121                            return 1
122                       
123        def waitPrinting(self) :
124            """Waits for printer status being 'printing'."""
125            firstvalue = None
126            while 1:
127                self.retrieveSNMPValues()
128                statusAsString = printerStatusValues.get(self.printerStatus)
129                if statusAsString in ('printing', 'warmup') :
130                    break
131                if self.printerInternalPageCounter is not None :   
132                    if firstvalue is None :
133                        # first time we retrieved a page counter, save it
134                        firstvalue = self.printerInternalPageCounter
135                    else :     
136                        # second time (or later)
137                        if firstvalue < self.printerInternalPageCounter :
138                            # Here we have a printer which lies :
139                            # it says it is not printing or warming up
140                            # BUT the page counter increases !!!
141                            # So we can probably quit being sure it is printing.
142                            self.parent.filter.printInfo("Printer %s is lying to us !!!" % self.parent.filter.printername, "warn")
143                            break
144                self.parent.filter.logdebug(_("Waiting for printer %s to be printing...") % self.parent.filter.printername)   
145                time.sleep(ITERATIONDELAY)
146           
147        def waitIdle(self) :
148            """Waits for printer status being 'idle'."""
149            idle_num = idle_flag = 0
150            while 1 :
151                self.retrieveSNMPValues()
152                pstatusAsString = printerStatusValues.get(self.printerStatus)
153                dstatusAsString = deviceStatusValues.get(self.deviceStatus)
154                idle_flag = 0
155                if (pstatusAsString == 'idle') or \
156                   ((pstatusAsString == 'other') and \
157                    (dstatusAsString == 'running')) :
158                    idle_flag = 1       # Standby / Powersave is considered idle
159                if idle_flag :   
160                    idle_num += 1
161                    if idle_num > STABILIZATIONDELAY :
162                        # printer status is stable, we can exit
163                        break
164                else :   
165                    idle_num = 0
166                self.parent.filter.logdebug(_("Waiting for printer %s's idle status to stabilize...") % self.parent.filter.printername)   
167                time.sleep(ITERATIONDELAY)
168               
169        def retrieveInternalPageCounter(self) :
170            """Returns the page counter from the printer via internal SNMP handling."""
171            try :
172                if (os.environ.get("PYKOTASTATUS") != "CANCELLED") and \
173                   (os.environ.get("PYKOTAACTION") != "DENY") and \
174                   (os.environ.get("PYKOTAPHASE") == "AFTER") and \
175                   self.parent.filter.jobSizeBytes :
176                    self.waitPrinting()
177                self.waitIdle()   
178            except :   
179                if self.printerInternalPageCounter is None :
180                    raise
181                else :   
182                    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")
183            return self.printerInternalPageCounter
184           
185if __name__ == "__main__" :           
186    if len(sys.argv) != 2 :   
187        sys.stderr.write("Usage :  python  %s  printer_ip_address\n" % sys.argv[0])
188    else :   
189        def _(msg) :
190            return msg
191           
192        class fakeFilter :
193            def __init__(self) :
194                self.printername = "FakePrintQueue"
195                self.jobSizeBytes = 1
196               
197            def printInfo(self, msg, level="info") :
198                sys.stderr.write("%s : %s\n" % (level.upper(), msg))
199                sys.stderr.flush()
200               
201            def logdebug(self, msg) :   
202                self.printInfo(msg, "debug")
203               
204        class fakeAccounter :       
205            def __init__(self) :
206                self.filter = fakeFilter()
207                self.protocolHandler = Handler(self, sys.argv[1])
208           
209        acc = fakeAccounter()           
210        print "Internal page counter's value is : %s" % acc.protocolHandler.retrieveInternalPageCounter()
Note: See TracBrowser for help on using the browser.