Skip to content

Commit e280aa0

Browse files
authored
Bump pysmi version to 1.6.2 (#22648)
* Bump pysmi version to 1.6.2 and update code * Create custom HTTP reader class to handle non-UTF-8 characters in MIB files * Add tests for the trans_oper logic * ddev test --fmt * Fix lints * Fix issue with enum * Remove borrowed or untouched MIBs * Remove print * Fixes * Remove debug print * Fix * Remove useless * Remove useless * Replace - by _ * Add back custom HTTP reader * ddev test --fmt datadog_checks_dev * Add changelog * Address reviews * ddev test --fmt * Fix test
1 parent b37c422 commit e280aa0

4 files changed

Lines changed: 109 additions & 20 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Bump pysmi from 0.3.4 to 1.6.2 for the `generate-traps-db` command.

datadog_checks_dev/datadog_checks/dev/tooling/commands/meta/snmp/generate_profile.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def _compile_mib_to_json(mib, source_mib_directories, destination_directory, sou
270270
from pysmi.codegen import JsonCodeGen
271271
from pysmi.compiler import MibCompiler
272272
from pysmi.parser import SmiV1CompatParser
273-
from pysmi.reader import getReadersFromUrls
273+
from pysmi.reader import get_readers_from_urls
274274
from pysmi.searcher import AnyFileSearcher, StubSearcher
275275
from pysmi.writer import FileWriter
276276

@@ -282,22 +282,22 @@ def _compile_mib_to_json(mib, source_mib_directories, destination_directory, sou
282282

283283
code_generator = JsonCodeGen()
284284

285-
file_writer = FileWriter(destination_directory).setOptions(suffix='.json')
285+
file_writer = FileWriter(destination_directory).set_options(suffix='.json')
286286

287287
mib_compiler = MibCompiler(SmiV1CompatParser(tempdir=''), code_generator, file_writer)
288288

289289
# use source_mib_directories as mibs source
290290
sources = [source]
291291
sources.extend(source_mib_directories)
292-
mib_compiler.addSources(*getReadersFromUrls(*sources, **{'fuzzyMatching': True}))
292+
mib_compiler.add_sources(*get_readers_from_urls(*sources, fuzzy_matching=True))
293293

294-
searchers = [AnyFileSearcher(destination_directory).setOptions(exts=['.json']), StubSearcher(*mib_stubs)]
295-
mib_compiler.addSearchers(*searchers)
294+
searchers = [AnyFileSearcher(destination_directory).set_options(exts=['.json']), StubSearcher(*mib_stubs)]
295+
mib_compiler.add_searchers(*searchers)
296296

297297
# borrowers, aka compiled mibs source
298298
borrowers = [
299-
AnyFileBorrower(borrower_reader, genTexts=True).setOptions(exts=['.json'])
300-
for borrower_reader in getReadersFromUrls(*[compiled_mibs_path], **{'lowcaseMatching': False})
299+
AnyFileBorrower(borrower_reader, genTexts=True).set_options(exts=['.json'])
300+
for borrower_reader in get_readers_from_urls(*[compiled_mibs_path], lowcase_matching=False)
301301
]
302302
mib_compiler.addBorrowers(*borrowers)
303303

datadog_checks_dev/datadog_checks/dev/tooling/commands/meta/snmp/generate_traps_db.py

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from datadog_checks.dev.tooling.commands.console import (
1717
CONTEXT_SETTINGS,
1818
abort,
19+
echo_debug,
1920
echo_failure,
2021
echo_info,
2122
echo_success,
@@ -30,6 +31,11 @@
3031
ALLOWED_EXTENSIONS_BY_FORMAT = {"json": [".json"], "yaml": [".yml", ".yaml"]}
3132

3233

34+
def _name_for_output(name: str) -> str:
35+
"""Normalize trap/variable name for output: hyphens to underscores (pysmi 0.3 compatibility)."""
36+
return name.replace("-", "_")
37+
38+
3339
class MappingType(Enum):
3440
INTEGER = 0
3541
BITS = 1
@@ -105,16 +111,92 @@ def generate_traps_db(mib_sources, output_dir, output_file, output_format, no_de
105111
"""
106112
from pysmi.codegen import JsonCodeGen
107113
from pysmi.compiler import MibCompiler
114+
from pysmi.mibinfo import MibInfo
108115
from pysmi.parser import SmiV1CompatParser
109-
from pysmi.reader import getReadersFromUrls
116+
from pysmi.reader import get_readers_from_urls
117+
from pysmi.reader.httpclient import HttpReader
110118
from pysmi.searcher import AnyFileSearcher
111119
from pysmi.writer import FileWriter
112120

121+
# pysmi's HttpReader decodes HTTP response as UTF-8 only; MIBs with non-UTF-8 bytes
122+
# (e.g. https://github.com/DataDog/mibs.snmplabs.com/blob/master/asn1/A3COM-HUAWEI-DEVICE-MIB#L593)
123+
# raise UnicodeDecodeError and are reported as "missing".
124+
# Use a tolerant reader for HTTP(S) URLs that decodes with errors='replace'.
125+
126+
def _make_readers(sources, fuzzy_matching=True):
127+
readers = []
128+
for src in sources:
129+
if src.startswith('http://') or src.startswith('https://'):
130+
readers.append(_TolerantHttpReader(src, fuzzy_matching=fuzzy_matching))
131+
else:
132+
readers.extend(get_readers_from_urls(src, fuzzy_matching=fuzzy_matching))
133+
return readers
134+
135+
# This code is copied from https://github.com/lextudio/pysmi/blob/main/pysmi/reader/httpclient.py.
136+
# Except that it decodes the response content with errors='replace' for non-UTF-8 bytes.
137+
class _TolerantHttpReader(HttpReader):
138+
"""HttpReader that decodes response content with errors='replace' for non-UTF-8 bytes."""
139+
140+
def __init__(self, url, fuzzy_matching=True):
141+
super().__init__(url)
142+
self.set_options(fuzzy_matching=fuzzy_matching)
143+
144+
def get_data(self, mibname, **options):
145+
import sys
146+
import time
147+
148+
from pysmi import error
149+
from pysmi.compat import decode
150+
151+
headers = {"Accept": "text/plain", "User-Agent": self._user_agent}
152+
153+
mibname = decode(mibname)
154+
155+
echo_debug(f"looking for MIB {mibname}")
156+
157+
for mibalias, mibfile in self.get_mib_variants(mibname, **options):
158+
if self.MIB_MAGIC in self._url:
159+
url = self._url.replace(self.MIB_MAGIC, mibfile)
160+
else:
161+
url = self._url + mibfile
162+
163+
echo_debug(f"trying to fetch MIB from {url}")
164+
165+
try:
166+
response = self.session.get(url, headers=headers)
167+
168+
except Exception:
169+
echo_debug(f"failed to fetch MIB from {url}: {sys.exc_info()[1]}")
170+
continue
171+
172+
echo_debug(f"HTTP response {response.status_code}")
173+
174+
if response.status_code == 200:
175+
try:
176+
mtime = time.mktime(
177+
time.strptime(
178+
response.headers["Last-Modified"],
179+
"%a, %d %b %Y %H:%M:%S %Z",
180+
)
181+
)
182+
183+
except Exception:
184+
echo_debug(f"malformed HTTP headers: {sys.exc_info()[1]}")
185+
mtime = time.time()
186+
187+
echo_debug(f"fetching source MIB {url}, mtime {response.headers.get('Last-Modified', '')}")
188+
189+
return MibInfo(path=url, file=mibfile, name=mibalias, mtime=mtime), response.content.decode(
190+
"utf-8", errors='replace'
191+
)
192+
193+
raise error.PySmiReaderFileNotFoundError(f"source MIB {mibname} not found", reader=self)
194+
113195
if debug:
114196
set_debug()
115-
from pysmi import debug
197+
from pysmi import debug as pysmi_debug
116198

117-
debug.setLogger(debug.Debug('all'))
199+
pysmi_debug.setLogger(pysmi_debug.Debug('all'))
118200

119201
# Defaulting to github.com/DataDog/mibs.snmplabs.com/
120202
mib_sources = [mib_sources] if mib_sources else [MIB_SOURCE_URL]
@@ -144,12 +226,12 @@ def generate_traps_db(mib_sources, output_dir, output_file, output_format, no_de
144226
mib_sources = sorted({pathlib.Path(x).parent.as_uri() for x in mib_files if os.path.sep in x}) + mib_sources
145227

146228
mib_files = [os.path.basename(x) for x in mib_files]
147-
searchers = [AnyFileSearcher(compiled_mibs_sources).setOptions(exts=['.json'])]
229+
searchers = [AnyFileSearcher(compiled_mibs_sources).set_options(exts=['.json'])]
148230
code_generator = JsonCodeGen()
149-
file_writer = FileWriter(compiled_mibs_sources).setOptions(suffix='.json')
231+
file_writer = FileWriter(compiled_mibs_sources).set_options(suffix='.json')
150232
mib_compiler = MibCompiler(SmiV1CompatParser(tempdir=''), code_generator, file_writer)
151-
mib_compiler.addSources(*getReadersFromUrls(*mib_sources, **{'fuzzyMatching': True}))
152-
mib_compiler.addSearchers(*searchers)
233+
mib_compiler.add_sources(*_make_readers(mib_sources, fuzzy_matching=True))
234+
mib_compiler.add_searchers(*searchers)
153235

154236
compiled_mibs, compiled_dependencies_mibs = compile_and_report_status(mib_files, mib_compiler)
155237

@@ -307,12 +389,13 @@ def generate_trap_db(compiled_mibs, compiled_mibs_sources, no_descr):
307389
trap_name = trap['name']
308390
trap_oid = trap['oid']
309391
trap_descr = trap.get('description', '')
310-
trap_db["traps"][trap_oid] = {"name": trap_name, "mib": file_mib_name}
392+
trap_db["traps"][trap_oid] = {"name": _name_for_output(trap_name), "mib": file_mib_name}
311393
if not no_descr:
312394
trap_db["traps"][trap_oid]["descr"] = trap_descr
313395
for trap_var in trap.get('objects', []):
314396
try:
315-
var_name, mib_name = trap_var['object'], trap_var['module']
397+
var_name = trap_var['object']
398+
mib_name = trap_var['module']
316399
var_metadata = get_var_metadata(
317400
var_name,
318401
mib_name,
@@ -331,8 +414,7 @@ def generate_trap_db(compiled_mibs, compiled_mibs_sources, no_descr):
331414
"Ignoring this variable.".format(trap_name, var_name, mib_name)
332415
)
333416
continue
334-
var_name = trap_var['object']
335-
trap_db["vars"][var_metadata.oid] = {"name": var_name}
417+
trap_db["vars"][var_metadata.oid] = {"name": _name_for_output(var_name)}
336418
if not no_descr:
337419
trap_db["vars"][var_metadata.oid]["descr"] = var_metadata.description
338420
if var_metadata.enum:
@@ -430,8 +512,14 @@ def get_mapping(var_name, mib_name, mapping_type: MappingType, search_locations=
430512
:param search_locations: Tuple of path to directories containing json-compiled MIB files
431513
:return: The oid and the description of the variable.
432514
"""
515+
visited = set() # Guard against circular type references (would cause infinite loop)
433516
mapping = {}
434517
while mib_name and var_name and not mapping:
518+
key = (var_name, mib_name)
519+
if key in visited:
520+
break
521+
visited.add(key)
522+
435523
for location in search_locations:
436524
file_name = os.path.join(location, mib_name + '.json')
437525
if os.path.isfile(file_name):
@@ -481,7 +569,7 @@ def get_import_mib(var_name, mib_name, search_locations=None):
481569
if os.path.isfile(file_name):
482570
break
483571
else:
484-
return MissingMIBException
572+
raise MissingMIBException()
485573

486574
with open(file_name, 'r') as f:
487575
file_content = json.load(f)

datadog_checks_dev/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ cli = [
6868
"pathspec>=0.10.0",
6969
"platformdirs>=2.0.0a3",
7070
"pydantic>=2.0.2",
71-
"pysmi==0.3.4",
71+
"pysmi==1.6.2",
7272
"securesystemslib[crypto]==0.28.0",
7373
"semver>=2.13.0",
7474
"tabulate>=0.8.9",

0 commit comments

Comments
 (0)