Search code examples
pythonpython-asyncionet-snmppysnmp

asyncio code in pysnmp is not responding as expected


I am new to pysnmp library. I tried the sample code provide in the documentation in snmplabs with certain modification as shown below.

import asyncio
from pysnmp.hlapi.asyncio import *
@asyncio.coroutine
def run(host,oid):
    errorIndication, errorStatus, errorIndex, varBinds = yield from getCmd(
        SnmpEngine(),
        CommunityData('public'),
        UdpTransportTarget((host, 161)),
        ContextData(),
        ObjectType(ObjectIdentity(oid))
    )
    print(errorIndication, errorStatus, errorIndex, varBinds)


asyncio.get_event_loop().run_until_complete(run('demo.snmplabs.com','1.3.6.1.2.1.1.1.0'))
print("asynch_1")
asyncio.get_event_loop().run_until_complete(run('198.155.104.8','1.3.6.1.2.1.1.1.0'))
print("asynch_2")
asyncio.get_event_loop().run_until_complete(run('snmp.live.gambitcommunications.com','1.3.6.1.2.1.1.1.0'))
print("asynch_3")

In the above i tried to query get-command for different agents. In which "198.155.104.8" is a dummy agent ip which doesnt exists. I expect the out as

None 0 0 [ObjectType(ObjectIdentity(<ObjectName value object at 0x7fdaa071e400 tagSet <TagSet object at 0x7fdaa4760828 tags 0:0:6> payload [1.3.6.1.2.1.1.1.0]>), <DisplayString value object at 0x7fda9fcf8c88 tagSet <TagSet object at 0x7fdaa4760400 tags 0:0:4> subtypeSpec <ConstraintsIntersection object at 0x7fdaa085e7b8 consts <ValueSizeConstraint object at 0x7fdaa4710f28 consts 0, 65535>, <ValueSizeConstraint object at 0x7fdaa07a1fd0 consts 0, 255>, <ValueSizeConstraint object at 0x7fdaa085e780 consts 0, 255>> encoding iso-8859-1 payload [Linux zeus 4.8.6...11 CDT 2016 i686]>)]
asynch_1

None 0 0 [ObjectType(ObjectIdentity(<ObjectName value object at 0x7fda9fba2da0 tagSet <TagSet object at 0x7fdaa4760828 tags 0:0:6> payload [1.3.6.1.2.1.1.1.0]>), <DisplayString value object at 0x7fda9fbaa828 tagSet <TagSet object at 0x7fdaa4760400 tags 0:0:4> subtypeSpec <ConstraintsIntersection object at 0x7fda9fac1c88 consts <ValueSizeConstraint object at 0x7fdaa4710f28 consts 0, 65535>, <ValueSizeConstraint object at 0x7fda9f9e5cf8 consts 0, 255>, <ValueSizeConstraint object at 0x7fdaa36e4048 consts 0, 255>> encoding iso-8859-1 payload [Cisco Internetwo...5:14 by kellythw]>)]
asynch_3

No SNMP response received before timeout 0 0 []
asynch_2

Since there is no agent referring to "198.155.104.8" the code should not wait in the second request it should print the third request.

But i am getting the output as shown below


None 0 0 [ObjectType(ObjectIdentity(<ObjectName value object at 0x7fdaa071e400 tagSet <TagSet object at 0x7fdaa4760828 tags 0:0:6> payload [1.3.6.1.2.1.1.1.0]>), <DisplayString value object at 0x7fda9fcf8c88 tagSet <TagSet object at 0x7fdaa4760400 tags 0:0:4> subtypeSpec <ConstraintsIntersection object at 0x7fdaa085e7b8 consts <ValueSizeConstraint object at 0x7fdaa4710f28 consts 0, 65535>, <ValueSizeConstraint object at 0x7fdaa07a1fd0 consts 0, 255>, <ValueSizeConstraint object at 0x7fdaa085e780 consts 0, 255>> encoding iso-8859-1 payload [Linux zeus 4.8.6...11 CDT 2016 i686]>)]
asynch_1
No SNMP response received before timeout 0 0 []
asynch_2
None 0 0 [ObjectType(ObjectIdentity(<ObjectName value object at 0x7fda9fba2da0 tagSet <TagSet object at 0x7fdaa4760828 tags 0:0:6> payload [1.3.6.1.2.1.1.1.0]>), <DisplayString value object at 0x7fda9fbaa828 tagSet <TagSet object at 0x7fdaa4760400 tags 0:0:4> subtypeSpec <ConstraintsIntersection object at 0x7fda9fac1c88 consts <ValueSizeConstraint object at 0x7fdaa4710f28 consts 0, 65535>, <ValueSizeConstraint object at 0x7fda9f9e5cf8 consts 0, 255>, <ValueSizeConstraint object at 0x7fdaa36e4048 consts 0, 255>> encoding iso-8859-1 payload [Cisco Internetwo...5:14 by kellythw]>)]
asynch_3

Since i am new to snmp. I am not able to get through the solution for using asyncio code for querying multiple agent at a time.

Please help in understanding the problem. As well as rectify my code if a writing it in a wrong way.

Any help will be appreciable.

Thanks in advance


Solution

  • This answer will clarify why run_until_complete() blocks your code execution.

    In short you have to create a list of tasks (couroutines) for the loop and then run it asynchronously gathered altogether.

    With modern async / await syntax (Python 3.5+) your refactored code would look something like this:

    import asyncio
    from pysnmp.hlapi.asyncio import *
    
    async def run(host,oid):
        errorIndication, errorStatus, errorIndex, varBinds = await getCmd(
            SnmpEngine(),
            CommunityData('public'),
            UdpTransportTarget((host, 161)),
            ContextData(),
            ObjectType(ObjectIdentity(oid))
        )
        print(errorIndication, errorStatus, errorIndex, varBinds)
    
    async def main():
        tasks = []
        tasks.append(run('demo.snmplabs.com','1.3.6.1.2.1.1.1.0'))
        tasks.append(run('198.155.104.8','1.3.6.1.2.1.1.1.0'))
        tasks.append(run('snmp.live.gambitcommunications.com','1.3.6.1.2.1.1.1.0'))
        results = await asyncio.gather(*tasks)
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    Please take a look at this example by Ilya Etingof @ github

    As a side note: it's better to keep a single, reusable SnmpEngine object within your script/thread. This object is expensive to initialized, it holds various caches so re-creating it slows down pysnmp a great deal. (с) etingof

    that advice boosts the performance big time (~3 times faster). Below is another version of the code with additional errors handling and returning a readable result.

    import asyncio
    import pysnmp.hlapi.asyncio as snmp
    
    async def get(host,oid):
        result = []
        try:
            snmp_engine = snmp.SnmpEngine()
            response = await snmp.getCmd(snmp_engine,
                                         snmp.CommunityData('public'),
                                         snmp.UdpTransportTarget((host, 161)),
                                         snmp.ContextData(),
                                         snmp.ObjectType(snmp.ObjectIdentity(oid)))
    
            errorIndication, errorStatus, errorIndex, varBinds = response
    
            if errorIndication:                
                print(f'{host}: errorIndication: {errorIndication}')
            elif errorStatus:
                print('{}: {} at {}'.format(host, errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex) - 1][0] or '?'))
            else:
                for varBind in varBinds:
                    result.append([x.prettyPrint() for x in varBind])
    
            snmp_engine.transportDispatcher.closeDispatcher()
    
        except Exception as err:
            print (f'Error at SNMP get() due to {err}')
    
        finally:
            print(f'Get {host}, {oid}: {result}')
            return result
    
    async def main():
        tasks = []
        tasks.append(get('demo.snmplabs.com','1.3.6.1.2.1.1.1.0'))
        tasks.append(get('198.155.104.8','1.3.6.1.2.1.1.1.0'))
        tasks.append(get('snmp.live.gambitcommunications.com','1.3.6.1.2.1.1.1.0'))
        results = await asyncio.gather(*tasks)
        print(f'main() results: {results}')
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    For more understanding on asyncio please see tutorials below:

    Best wishes.