lextudio / pysnmp

Python SNMP library
https://www.pysnmp.com/pysnmp/
BSD 2-Clause "Simplified" License
87 stars 24 forks source link

LLDP-MIB - Cannot create a consistent method resolution #90

Closed steven-douilliet closed 2 months ago

steven-douilliet commented 3 months ago

Environment

Package Version
pyasn1 0.6.0
pysnmp-lextudio 6.2.1
pysmi 1.4.4
ply 3.20.0
pycryptodomex 1.4.4

Steps to Reproduce

  1. Install all dependencies listed above.
  2. (a) Run an SNMP command (walk, get, etc) using LLDP-MIB:
snmpwalk -v2c -c public 172.16.10.100 LLDP-MIB::lldpRemTable
  1. (b) Or use this python script:
import asyncio
import json

from pysnmp.hlapi.asyncio import *

async def main():
    g = walkCmd(
        SnmpEngine(),
        CommunityData('public'),
        UdpTransportTarget(('172.16.10.100', 161)),
        ContextData(),
        ObjectType(ObjectIdentity("LLDP-MIB", "lldpRemTable")),
        lexicographicMode=False
    )
    entries = {}
    async for errorIndication, errorStatus, errorIndex, varBinds in g:
        if errorIndication:
            print(errorIndication)
        elif errorStatus:
            print(
                "{} at {}".format(
                    errorStatus.prettyPrint(),
                    errorIndex and varBinds[int(errorIndex) - 1][0] or "?",
                )
            )
        else:
            for oid, value in varBinds:
                index = (oid.asTuple()[-1])
                entry = entries.setdefault(index, {})
                entry[oid.getLabel()[-1]] = value.prettyPrint()
    print(json.dumps(entries))

asyncio.run(main())

Expected Behavior

LLDP-MIB::lldpRemTable is resolved and the table entries are returned. For example (using linux command snmptable):

$ snmptable -v2c -c public 172.16.10.100 LLDP-MIB::lldpRemTable
SNMP table: LLDP-MIB::lldpRemTable

 lldpRemChassisIdSubtype     lldpRemChassisId lldpRemPortIdSubtype lldpRemPortId lldpRemPortDesc lldpRemSysName                                                    lldpRemSysDesc lldpRemSysCapSupported lldpRemSysCapEnabled
              macAddress "0C 5F 95 0C F7 A1 "        interfaceName   "Ethernet1" Je suis le Goat        arista2 Arista Networks EOS version 4.29.8M running on an Arista vEOS-lab               "28 00 "             "20 00 "
              macAddress "0C C0 23 EC 73 30 "        interfaceName   "Ethernet1" Je suis le Boss      localhost Arista Networks EOS version 4.29.8M running on an Arista vEOS-lab               "28 00 "             "20 00 "
              macAddress "0C 5F 95 0C F7 A1 "        interfaceName "Management1"                        arista2 Arista Networks EOS version 4.29.8M running on an Arista vEOS-lab               "28 00 "             "20 00 "
              macAddress "0C C0 23 EC 73 30 "        interfaceName "Management1"                      localhost Arista Networks EOS version 4.29.8M running on an Arista vEOS-lab               "28 00 "             "20 00 "

Observed Behavior

A load error is occured with root cause the following python error:

Traceback (most recent call last):
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule
    exec(codeObj, g)
  File "/home/anonymous/.pysnmp/mibs/RMON2-MIB.py", line 153, in <module>
    class LastCreateTime(TextualConvention, TimeStamp):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases TextualConvention, TimeStamp

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule
    exec(codeObj, g)
  File "/home/anonymous/.pysnmp/mibs/LLDP-MIB.py", line 53, in <module>
    ZeroBasedCounter32) = mibBuilder.importSymbols(
                          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 464, in importSymbols
    self.loadModules(modName, **userCtx)
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 417, in loadModules
    self.loadModule(modName, **userCtx)
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 380, in loadModule
    raise error.MibLoadError(
pysnmp.smi.error.MibLoadError: MIB module '/home/anonymous/.pysnmp/mibs/RMON2-MIB/home/anonymous/.pysnmp/mibs/RMON2-MIB.py' load error: ['Traceback (most recent call last):\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule\n    exec(codeObj, g)\n', '  File "/home/anonymous/.pysnmp/mibs/RMON2-MIB.py", line 153, in <module>\n    class LastCreateTime(TextualConvention, TimeStamp):\n', 'TypeError: Cannot create a consistent method resolution\norder (MRO) for bases TextualConvention, TimeStamp\n'] caused by <class 'TypeError'>: Cannot create a consistent method resolution
order (MRO) for bases TextualConvention, TimeStamp

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/anonymous/nano-core/dev/apps/ssiapp/test.py", line 35, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.12/asyncio/runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/anonymous/nano-core/dev/apps/ssiapp/test.py", line 17, in main
    async for errorIndication, errorStatus, errorIndex, varBinds in g:
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/hlapi/asyncio/cmdgen.py", line 728, in walkCmd
    initialVars = [x[0] for x in vbProcessor.makeVarBinds(snmpEngine, varBinds)]
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/hlapi/asyncio/varbinds.py", line 41, in makeVarBinds
    varBind.resolveWithMib(mibViewController, ignoreErrors=False)
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/rfc1902.py", line 937, in resolveWithMib
    self.__args[0].resolveWithMib(mibViewController)
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/rfc1902.py", line 506, in resolveWithMib
    (mibNode,) = mibViewController.mibBuilder.importSymbols(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 464, in importSymbols
    self.loadModules(modName, **userCtx)
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 417, in loadModules
    self.loadModule(modName, **userCtx)
  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 380, in loadModule
    raise error.MibLoadError(
pysnmp.smi.error.MibLoadError: MIB module '/home/anonymous/.pysnmp/mibs/LLDP-MIB/home/anonymous/.pysnmp/mibs/LLDP-MIB.py' load error: ['Traceback (most recent call last):\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule\n    exec(codeObj, g)\n', '  File "/home/anonymous/.pysnmp/mibs/RMON2-MIB.py", line 153, in <module>\n    class LastCreateTime(TextualConvention, TimeStamp):\n', 'TypeError: Cannot create a consistent method resolution\norder (MRO) for bases TextualConvention, TimeStamp\n', '\nDuring handling of the above exception, another exception occurred:\n\n', 'Traceback (most recent call last):\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule\n    exec(codeObj, g)\n', '  File "/home/anonymous/.pysnmp/mibs/LLDP-MIB.py", line 53, in <module>\n    ZeroBasedCounter32) = mibBuilder.importSymbols(\n                          ^^^^^^^^^^^^^^^^^^^^^^^^^\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 464, in importSymbols\n    self.loadModules(modName, **userCtx)\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 417, in loadModules\n    self.loadModule(modName, **userCtx)\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 380, in loadModule\n    raise error.MibLoadError(\n', 'pysnmp.smi.error.MibLoadError: MIB module \'/home/anonymous/.pysnmp/mibs/RMON2-MIB/home/anonymous/.pysnmp/mibs/RMON2-MIB.py\' load error: [\'Traceback (most recent call last):\\n\', \'  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule\\n    exec(codeObj, g)\\n\', \'  File "/home/anonymous/.pysnmp/mibs/RMON2-MIB.py", line 153, in <module>\\n    class LastCreateTime(TextualConvention, TimeStamp):\\n\', \'TypeError: Cannot create a consistent method resolution\\norder (MRO) for bases TextualConvention, TimeStamp\\n\'] caused by <class \'TypeError\'>: Cannot create a consistent method resolution\norder (MRO) for bases TextualConvention, TimeStamp\n'] caused by <class 'pysnmp.smi.error.MibLoadError'>: MIB module '/home/anonymous/.pysnmp/mibs/RMON2-MIB/home/anonymous/.pysnmp/mibs/RMON2-MIB.py' load error: ['Traceback (most recent call last):\n', '  File "/home/anonymous/nano-core/venv/lib/python3.12/site-packages/pysnmp/smi/builder.py", line 376, in loadModule\n    exec(codeObj, g)\n', '  File "/home/anonymous/.pysnmp/mibs/RMON2-MIB.py", line 153, in <module>\n    class LastCreateTime(TextualConvention, TimeStamp):\n', 'TypeError: Cannot create a consistent method resolution\norder (MRO) for bases TextualConvention, TimeStamp\n'] caused by <class 'TypeError'>: Cannot create a consistent method resolution
order (MRO) for bases TextualConvention, TimeStamp

The root cause is the TypeError: Cannot create a consistent method resolution, which indicates an inheritance issue.

Please note that when using another MIB, such as IF-MIB::ifTable, the resolution works correctly and no errors occur.

lextm commented 3 months ago

Similar to #91, this is a bug in PySMI where it should further analyze the generated types and avoid duplicate inheritance.

While the bugfix won't come soon, the manual workaround is also simple,

  1. Open up RMON2-MIB.py in a text editor.
  2. Change class LastCreateTime(TextualConvention, TimeStamp) to class LastCreateTime(TimeStamp).

BTW, please switch from pysnmp-lextudio to pysnmp, as we won't update the former any more.

lextm commented 2 months ago

Close it now as won't work on it in near future.

mmakaay commented 2 months ago

We ran into this issue too, with the MIB compilation step in our application after switching to Python 3.12.

Manually fixing the .py file was no full work-around, because we generate the .py files on application startup in our Docker container, based on a plugin system (where each plugin can provide extra MIBs when required). Therefore, I automated the process of fixing the code, by leveraging the Python ast package.

As possible inspiration for others that run into the same issue, here the gist of the method in our compiler that takes care of fixing the generate pysnmp MIB files that are stored in the output_path directory. When there are more patterns to fix, these can be added quickly to this skeleton.

import ast
from pathlib import Path

class VisitorThatFixesMroIssue(ast.NodeTransformer):
    def __init__(self) -> None:
        super().__init__()
        self.did_fix_code = False

        def visit_ClassDef(self, node: ast.ClassDef) -> ast.AST:
            base_names = [base.id for base in node.bases if isinstance(base, ast.Name)]
            if base_names == ['TextualConvention', 'DisplayString']:
                node.bases = [node.bases[1]]
                self.did_fix_code = True
            return self.generic_visit(node)

def fix_mib_py_file(file_path: Path) -> None:
      tree = ast.parse(source=file_path.read_text())
      visitor = VisitorThatFixesMroIssue()
      visitor.visit(tree)
      if visitor.did_fix_code:
          file_path.write_text(ast.unparse(tree))

Just an idea

A quick fix for the pysmi code, might be to apply the above change right from the code generating template at https://github.com/lextudio/pysmi/blob/main/pysmi/codegen/templates/pysnmp/mib-definitions.j2#L219 The generating template code could could be something like this:

{%if definition['type']['type'] == 'DisplayString' %}
class {{ symbol }}({{ definition['type']['type'] }}):
{% else %}
class {{ symbol }}(TextualConvention, {{ definition['type']['type'] }}):
{% endif %}
lextm commented 2 months ago

Should have been fixed in PySMI release 1.5.

mmakaay commented 2 months ago

Yes, I can confirm that it works for the issues that I ran into. Upgrading to 1.5.0 fully vanished them. Thanks!

steven-douilliet commented 2 months ago

Hello, That it works! Thanks