1
2
3
4
5
6
7
8
9
10
11
12
13 __doc__= """zenmib
14 The zenmib program converts MIBs into python data structures and then
15 (by default) adds the data to the Zenoss DMD. Essentially, zenmib is a
16 wrapper program around the smidump program, whose output (python code) is
17 then executed "inside" the Zope database.
18
19 Overview of terms:
20 SNMP Simple Network Management Protocol
21 A network protocol originally based on UDP which allows a management
22 application (ie SNMP manager) to contact and get information from a
23 device (ie router, computer, network-capable toaster). The software
24 on the device that is capable of understanding SNMP and responding
25 appropriately is called an SNMP agent.
26
27 MIB Management of Information Base
28 A description of what a particular SNMP agent provides and what
29 traps it sends. A MIB is a part of a tree structure based on a root
30 MIB. Since a MIB is a rooted tree, it allows for delegation of areas
31 under the tree to different organizations.
32
33 ASN Abstract Syntax Notation
34 The notation used to construct a MIB.
35
36 OID Object IDentifier
37 A MIB is constructed of unique identifiers
38
39
40 Background information:
41 http://en.wikipedia.org/wiki/Simple_Network_Management_Protocol
42 Overview of SNMP.
43
44 http://www.ibr.cs.tu-bs.de/projects/libsmi/index.html?lang=en
45 The libsmi project is the creator of smidump. There are several
46 interesting sub-projects available.
47
48 http://net-snmp.sourceforge.net/
49 Homepage for Net-SNMP which is used by Zenoss for SNMP management.
50
51 http://www.et.put.poznan.pl/snmp/asn1/asn1.html
52 An overview of Abstract Syntax Notation (ASN), the language in
53 which MIBs are written.
54 """
55
56 import os
57 import os.path
58 import sys
59 import glob
60 import re
61 from subprocess import Popen, PIPE
62
63 import Globals
64 import transaction
65
66 from Products.ZenUtils.ZCmdBase import ZCmdBase
67 from Products.ZenUtils.Utils import zenPath
68 from zExceptions import BadRequest
69
70
72 """
73 Generator function to create a list of absolute paths
74 for all files in a directory tree starting from the list
75 of directories given as arguments to this function.
76
77 @param *dirs: list of directories to investigate
78 @type *dirs: list of strings
79 @return: directory to investigate
80 @rtype: string
81 """
82 for dir in dirs:
83 for dirname, _, filenames in os.walk(dir):
84 for filename in filenames:
85 yield os.path.join(dirname, filename)
86
87
88
90 """
91 A dependency is a reference to another part of the MIB tree.
92 All MIB definitions start from the base of the tree (ie .1).
93 Generally dependencies are from MIB definitions external to
94 the MIB under inspection.
95 """
96
98 self.fileMap = {}
99 self.depMap = {}
100
101
102 - def add(self, filename, name, dependencies):
103 """
104 Add a dependency to the dependency tree if it's not already there.
105
106 @param filename: name of MIB file to import
107 @type filename: string
108 @param name: name of MIB
109 @type name: string
110 @param dependencies: dependency
111 @type dependencies: dependency object
112 """
113 if name not in self.depMap:
114 self.fileMap[filename] = name
115 self.depMap[name] = (filename, dependencies)
116
117
119 """
120 Given a filename, return the name of the MIB tree defined in it.
121 Makes the assumption that there's only one MIB tree per file.
122
123 @param filename: MIB filename
124 @type filename: string
125 @return: MIB name
126 @rtype: string
127 @todo: to allow for multiple MIBs in a file, should return a list
128 """
129 return self.fileMap.get(filename, None)
130
131
133 """
134 Given a name of the MIB tree, return the filename that it's from
135 and its dependencies.
136
137 @param name: name of MIB
138 @type name: string
139 @return: dependency tree
140 @rtype: tuple of ( name, dependency object)
141 """
142 return self.depMap.get(name, None)
143
144
145
147 """
148 Wrapper around the smidump utilities to load MIB files into
149 the DMD tree.
150 """
152 """
153 Scan the MIB file to determine what MIB trees the file is dependent on.
154
155 @param mibfile: MIB filename
156 @type mibfile: string
157 @return: dependency tree
158 @rtype: tuple of ( name, dependency object)
159 """
160
161 fp = open(mibfile)
162 mib = fp.read()
163 fp.close()
164 return self.map_mib_to_dependents(mib)
165
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 DEFINITIONS = re.compile(r'(?P<mib_name>[A-Za-z-0-9]+) +DEFINITIONS *::= *BEGIN')
184 DEPENDS = re.compile(r'FROM *(?P<mib_dependency>[A-Za-z-0-9]+)')
185
186
187
188
189
190 parts = mib.split('OBJECT IDENTIFIER', 1)
191 mib_prologue = parts[0]
192 match = DEFINITIONS.search(mib_prologue)
193 if not match:
194
195 return None, []
196
197
198
199
200
201
202
203
204
205
206 depends = []
207 name = match.group('mib_name')
208 start = match.end(0)
209 while 1:
210 match = DEPENDS.search(mib_prologue, start)
211 if not match:
212 break
213
214 depends.append(match.group('mib_dependency'))
215
216
217 start = match.end(0)
218
219 return name, depends
220
221
222
224 """
225 Create a dependency map for all MIB files.
226 Exit the program if we're missing any files.
227
228 @param filenames: names of MIB files to import
229 @type filenames: list of strings
230 @return: dependency tree
231 @rtype: DependencyMap
232 """
233 missing_files = 0
234 result = DependencyMap()
235 for filename in filenames:
236 try:
237 defines, depends = self.map_file_to_dependents(filename)
238
239 except IOError:
240 self.log.error( "Couldn't open file %s", filename)
241 missing_files += 1
242 continue
243
244 if defines == None:
245 self.log.debug( "Unable to parse information from %s -- skipping", filename)
246 else:
247 result.add(filename, defines, depends)
248
249 if missing_files > 0:
250 self.log.error( "Missing %s files", missing_files )
251 sys.exit(1)
252
253 return result
254
255
256
258 """
259 smidump needs to know the list of dependent files for a MIB file in
260 order to properly resolve external references.
261
262 @param filename: name of MIB file to import
263 @type filename: string
264 @param depMap: dependency tree
265 @type depMap: DependencyMap
266 @return: list of dependencies
267 @rtype: list of strings
268 """
269
270
271 name = depMap.getName(filename)
272 if not name:
273 return []
274
275
276 deps = []
277 def dependency_search(name):
278 """
279 Create a list of files required by an OID.
280
281 @param name: name of OID
282 @type name: string
283 """
284 fileAndDeps = depMap.getDependencies(name)
285 if not fileAndDeps:
286 self.log.warn( "Unable to find a file providing the OID %s", name)
287 return
288
289 mib_file, dependent_oids = fileAndDeps
290 if mib_file and mib_file not in deps:
291 deps.append(mib_file)
292
293 for unresolved_oid in dependent_oids:
294 dependency_search(unresolved_oid)
295
296
297 dependency_search(name)
298 if deps[1:]:
299 return deps[1:]
300
301 return []
302
303
304
306 """
307 Use the smidump program to convert a MIB into Python code"
308
309 @param mibname: name of the MIB
310 @type mibname: string
311 @param dependencies: list of dependent files
312 @type dependencies: list of strings
313 @return: the newly created MIB
314 @rtype: MIB object
315 """
316 dump_command = [ 'smidump', '-k', '-fpython' ]
317 for dep in dependencies[1:]:
318
319 dump_command.append( '-p')
320 dump_command.append( dep )
321
322 dump_command.append( mibname )
323 self.log.debug('Running %s', ' '.join( dump_command))
324 proc = Popen( dump_command, stdout=PIPE, stderr=PIPE )
325
326 python_code, warnings = proc.communicate()
327 proc.wait()
328 if proc.returncode:
329 self.log.error(warnings)
330 return None
331
332 if len(warnings) > 0:
333 self.log.debug("Found warnings while trying to import MIB:\n%s" \
334 % warnings)
335
336
337
338 result = {}
339 try:
340 exec python_code in result
341
342 except (SystemExit, KeyboardInterrupt): raise
343 except:
344 self.log.exception("Unable to import Pythonized-MIB: %s", mibname)
345 return None
346
347
348 mib = result.get( 'MIB', None)
349 return mib
350
351
352
353
354 MIB_MOD_ATTS = ('language', 'contact', 'description')
355
356 - def load_mib(self, mibs, mibname, depmap):
357 """
358 Attempt to load a MIB after generating its dependency tree
359
360 @param mibs: filenames of the MIBs to load
361 @type mibs: list of strings
362 @param mibname: name of the MIB
363 @type mibname: string
364 @param dependencies: list of dependent files
365 @type dependencies: string
366 @return: whether the MIB load was successful or not
367 @rtype: boolean
368 """
369 dependencies = self.getDependencies(mibname, depmap)
370
371 mib = self.generate_python_from_mib( mibname, dependencies )
372 if not mib:
373 return False
374
375
376 modname = mib['moduleName']
377
378
379 mod = None
380 if mod:
381 self.log.warn( "Skipping %s as it is already loaded", modname)
382 return False
383
384
385
386
387
388 mod = mibs.createMibModule(modname, self.options.path)
389 for key, val in mib[modname].items():
390 if key in self.MIB_MOD_ATTS:
391 setattr(mod, key, val)
392
393
394 nodes_added = 0
395 if 'nodes' in mib:
396 for name, values in mib['nodes'].items():
397 try:
398 mod.createMibNode(name, **values)
399 nodes_added += 1
400 except BadRequest:
401 try:
402 self.log.warn("Unable to add node id '%s' as this"
403 " name is reserved for use by Zope",
404 name)
405 newName = '_'.join([name, modname])
406 self.log.warn("Trying to add node '%s' as '%s'",
407 name, newName)
408 mod.createMibNode(newName, **values)
409 self.log.warn("Renamed '%s' to '%s' and added to MIB"
410 " nodes", name, newName)
411 except:
412 self.log.warn("Unable to add '%s' -- skipping",
413 name)
414
415
416 traps_added = 0
417 if 'notifications' in mib:
418 for name, values in mib['notifications'].items():
419 try:
420 mod.createMibNotification(name, **values)
421 traps_added += 1
422 except BadRequest:
423 try:
424 self.log.warn("Unable to add trap id '%s' as this"
425 " name is reserved for use by Zope",
426 name)
427 newName = '_'.join([name, modname])
428 self.log.warn("Trying to add trap '%s' as '%s'",
429 name, newName)
430 mod.createMibNotification(newName, **values)
431 self.log.warn("Renamed '%s' to '%s' and added to MIB"
432 " traps", name, newName)
433 except:
434 self.log.warn("Unable to add '%s' -- skipping",
435 name)
436
437 self.log.info("Parsed %d nodes and %d notifications", nodes_added,
438 traps_added)
439
440
441
442 if not self.options.nocommit:
443 trans = transaction.get()
444 trans.setUser( "zenmib" )
445 trans.note("Loaded MIB %s into the DMD" % modname)
446 trans.commit()
447 self.log.info("Loaded MIB %s into the DMD", modname)
448
449 return True
450
451
452
454 """
455 Main loop of the program
456 """
457
458 smimibdir = self.options.mibsdir
459 if not os.path.exists( smimibdir ):
460 self.log.error("The directory %s doesn't exist!" % smimibdir )
461 sys.exit(1)
462
463 ietf, iana, irtf, tubs, site = \
464 map(lambda x: os.path.join(smimibdir, x),
465 'ietf iana irtf tubs site'.split())
466
467
468
469 if len(self.args) > 0:
470 mibnames = self.args
471 depMap = self.dependencies(list(walk(ietf, iana, irtf, tubs))
472 + mibnames)
473 else:
474 mibnames = glob.glob(os.path.join(smimibdir, 'site', '*'))
475 depMap = self.dependencies(walk(ietf, iana, irtf, tubs, site))
476
477
478 mibs = self.dmd.Mibs
479
480
481 loaded_mib_files = 0
482 for mibname in mibnames:
483 try:
484 if self.load_mib( mibs, mibname, depMap):
485 loaded_mib_files += 1
486
487 except (SystemExit, KeyboardInterrupt): raise
488 except Exception, ex:
489 self.log.exception("Failed to load MIB: %s" % mibname)
490
491 action = "Loaded"
492 if self.options.nocommit:
493 action = "Processed"
494 self.log.info( "%s %d MIB file(s)" % ( action, loaded_mib_files))
495 sys.exit(0)
496
497
499 """
500 Command-line options
501 """
502 ZCmdBase.buildOptions(self)
503 self.parser.add_option('--mibsdir',
504 dest='mibsdir', default=zenPath('share/mibs'),
505 help="Directory of input MIB files [ default: %default ]")
506 self.parser.add_option('--path',
507 dest='path', default="/",
508 help="Path to load MIB into the DMD")
509 self.parser.add_option('--nocommit', action='store_true',
510 dest='nocommit', default=False,
511 help="Don't commit the MIB to the DMD after loading")
512
513
514 if __name__ == '__main__':
515 zm = zenmib()
516 zm.main()
517