1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__ = """zendisc
15 Scan networks and routes looking for devices to add to the ZODB
16 """
17
18 import socket
19
20 import Globals
21 from optparse import SUPPRESS_HELP
22
23 from Products.DataCollector.zenmodeler import ZenModeler
24 from Products.ZenUtils.Exceptions import ZentinelException
25 from Products.ZenUtils.Utils import unused
26 from Products.ZenUtils.Driver import drive
27 from Products.ZenUtils.IpUtil import asyncNameLookup
28 from Products.ZenUtils.IpUtil import isip
29 from Products.ZenUtils.IpUtil import parse_iprange
30 from Products.ZenUtils.NJobs import NJobs
31 from Products.ZenModel.Exceptions import NoIPAddress
32 from Products.ZenEvents.ZenEventClasses import Status_Snmp
33 from Products.ZenEvents.Event import Info
34 from Products.ZenStatus.AsyncPing import Ping
35 from Products.ZenHub.PBDaemon import FakeRemote, PBDaemon
36 from Products.ZenHub.services import DiscoverService, ModelerService
37 unused(DiscoverService, ModelerService)
38
39
40 from twisted.internet.defer import succeed
41 from twisted.python.failure import Failure
42 from twisted.internet import reactor
43 from twisted.names.error import DNSNameError
44
45
47 """
48 Scan networks and routes looking for devices to add to the ZODB
49 """
50
51 initialServices = PBDaemon.initialServices + ['DiscoverService']
52 name = 'zendisc'
53 scanned = 0
54
56 """
57 Initalizer
58
59 @param single: collect from a single device?
60 @type single: boolean
61 """
62 ZenModeler.__init__(self, single )
63 if not self.options.useFileDescriptor:
64 self.openPrivilegedPort('--ping')
65 self.discovered = []
66 sock = None
67 if self.options.useFileDescriptor:
68 sock = int(self.options.useFileDescriptor)
69 self.ping = Ping(self.options.tries,
70 self.options.timeout,
71 sock=sock)
72
73
75 """
76 Get the DiscoverService
77
78 @return: a DiscoverService from zenhub
79 @rtype: function
80 """
81 return self.services.get('DiscoverService', FakeRemote())
82
83
85 """
86 Ping all ips, create entries in the network if necessary.
87
88 @param nets: list of networks to discover
89 @type nets: list
90 @return: successful result is a list of IPs that were added
91 @rtype: Twisted deferred
92 """
93 def inner(driver):
94 """
95 Twisted driver class to iterate through devices
96
97 @param driver: Zenoss driver
98 @type driver: Zenoss driver
99 @return: successful result is a list of IPs that were added
100 @rtype: Twisted deferred
101 """
102 ips = []
103 goodCount = 0
104
105 for net in nets:
106 if self.options.subnets and len(net.children()) > 0:
107 continue
108 if not getattr(net, "zAutoDiscover", False):
109 self.log.info(
110 "Skipping network %s because zAutoDiscover is False"
111 % net.getNetworkName())
112 continue
113 self.log.info("Discover network '%s'", net.getNetworkName())
114 yield NJobs(self.options.chunkSize,
115 self.ping.ping,
116 net.fullIpList()).start()
117 results = driver.next()
118 goodips = [
119 v.ipaddr for v in results if not isinstance(v, Failure)]
120 badips = [
121 v.value.ipaddr for v in results if isinstance(v, Failure)]
122 goodCount += len(goodips)
123 self.log.debug("Got %d good IPs and %d bad IPs",
124 len(goodips), len(badips))
125 yield self.config().callRemote('pingStatus',
126 net,
127 goodips,
128 badips,
129 self.options.resetPtr,
130 self.options.addInactive)
131 ips += driver.next()
132 self.log.info("Discovered %s active ips", goodCount)
133
134 yield succeed(ips)
135 driver.next()
136
137 d = drive(inner)
138 return d
139
141 """
142 Ping all IPs in the range and create devices for the ones that come
143 back.
144
145 @param ranges: list of ranges to discover
146 @type ranges: list
147 """
148 if isinstance(self.options.range, basestring):
149 self.options.range = [self.options.range]
150
151
152 if (isinstance(self.options.range, list) and
153 self.options.range[0].find(",") > -1):
154 self.options.range = [n.strip() for n in
155 self.options.range[0].split(',')]
156 ips = []
157 goodCount = 0
158 for iprange in self.options.range:
159
160 ips.extend(parse_iprange(iprange))
161 yield NJobs(self.options.chunkSize,
162 self.ping.ping,
163 ips).start()
164 results = driver.next()
165 goodips = [v.ipaddr for v in results if not isinstance(v, Failure)]
166 badips = [v.value.ipaddr for v in results if isinstance(v, Failure)]
167 goodCount += len(goodips)
168 self.log.debug("Got %d good IPs and %d bad IPs",
169 len(goodips), len(badips))
170 yield self.discoverDevices(goodips)
171 yield succeed("Discovered %d active IPs" % goodCount)
172 driver.next()
173
174
176 """
177 Discover all default routers based on DMD configuration.
178
179 @param rootdev: device root in DMD
180 @type rootdev: device class
181 @param seenips: list of IP addresses
182 @type seenips: list of strings
183 @return: Twisted/Zenoss Python iterable
184 @rtype: Python iterable
185 """
186 if not seenips:
187 seenips = []
188
189 def inner(driver):
190 """
191 Twisted driver class to iterate through devices
192
193 @param driver: Zenoss driver
194 @type driver: Zenoss driver
195 @return: successful result is a list of IPs that were added
196 @rtype: Twisted deferred
197 """
198 yield self.config().callRemote('followNextHopIps', rootdev.id)
199 for ip in driver.next():
200 if ip in seenips:
201 continue
202 self.log.info("device '%s' next hop '%s'", rootdev.id, ip)
203 seenips.append(ip)
204 yield self.discoverDevice(ip, devicepath="/Network/Router")
205 router = driver.next()
206 if not router:
207 continue
208 yield self.discoverRouters(router, seenips)
209 driver.next()
210
211 return drive(inner)
212
213
215 """
216 Send a 'device discovered' event through zenhub
217
218 @param ip: IP addresses
219 @type ip: strings
220 @param dev: remote device name
221 @type dev: device object
222 @param sev: severity
223 @type sev: integer
224 """
225 devname = comp = ip
226 if dev:
227 devname = dev.id
228 msg = "'Discovered device name '%s' for ip '%s'" % (devname, ip)
229 evt = dict(device=devname,ipAddress=ip,eventKey=ip,
230 component=comp,eventClass=Status_Snmp,
231 summary=msg, severity=sev,
232 agent="Discover")
233 self.sendEvent(evt)
234
235
236 - def discoverDevices(self,
237 ips,
238 devicepath="/Discovered",
239 prodState=1000):
240 """
241 Discover devices by active ips that are not associated with a device.
242
243 @param ips: list of IP addresses
244 @type ips: list of strings
245 @param devicepath: where in the DMD to put any discovered devices
246 @type devicepath: string
247 @param prodState: production state (see Admin Guide for a description)
248 @type prodState: integer
249 @return: Twisted/Zenoss Python iterable
250 @rtype: Python iterable
251 """
252 def discoverDevice(ip):
253 """
254 Discover a particular device
255 NB: Wrapper around self.discoverDevice()
256
257 @param ip: IP address
258 @type ip: string
259 @return: Twisted/Zenoss Python iterable
260 @rtype: Python iterable
261 """
262 return self.discoverDevice(ip, devicepath, prodState)
263
264 return NJobs(self.options.parallel, discoverDevice, ips).start()
265
266
268 """
269 Scan a device for ways of naming it: PTR DNS record or a SNMP name
270
271 @param ip: IP address
272 @type ip: string
273 @param devicePath: where in the DMD to put any discovered devices
274 @type devicePath: string
275 @param deviceSnmpCommunities: Optional list of SNMP community strings
276 to try, overriding those set on the device class
277 @type deviceSnmpCommunities: list
278 @return: result is None or a tuple containing
279 (community, port, version, snmp name)
280 @rtype: deferred: Twisted deferred
281 """
282 from pynetsnmp.twistedsnmp import AgentProxy
283
284 def inner(driver):
285 """
286 Twisted driver class to iterate through devices
287
288 @param driver: Zenoss driver
289 @type driver: Zenoss driver
290 @return: successful result is a list of IPs that were added
291 @rtype: Twisted deferred
292 """
293 self.log.debug("Doing SNMP lookup on device %s", ip)
294 yield self.config().callRemote('getSnmpConfig', devicePath)
295 communities, port, version, timeout, retries = driver.next()
296
297
298
299 if deviceSnmpCommunities is not None:
300 communities = deviceSnmpCommunities
301
302 oid = ".1.3.6.1.2.1.1.5.0"
303 goodcommunity = ""
304 goodversion = ""
305 devname = ""
306 for version in ("v2c", "v1"):
307 for community in communities:
308 proxy = AgentProxy(ip,
309 port,
310 timeout=timeout,
311 community=community,
312 snmpVersion=version,
313 tries=retries - 1)
314 proxy.open()
315 try:
316 yield proxy.get([oid])
317 devname = driver.next().values()[0]
318 goodcommunity = community
319 goodversion = version
320 break
321 except:
322 pass
323 proxy.close()
324 if goodcommunity:
325 yield succeed((goodcommunity, port, goodversion, devname))
326 break
327 else:
328 yield succeed(None)
329 driver.next()
330 self.log.debug("Finished SNMP lookup on device %s", ip)
331
332 return drive(inner)
333
334
335 - def discoverDevice(self, ip, devicepath="/Discovered", prodState=1000):
336 """
337 Discover a device based on its IP address.
338
339 @param ip: IP address
340 @type ip: string
341 @param devicepath: where in the DMD to put any discovered devices
342 @type devicepath: string
343 @param prodState: production state (see Admin Guide for a description)
344 @type prodState: integer
345 @return: Twisted/Zenoss Python iterable
346 @rtype: Python iterable
347 """
348 self.scanned += 1
349 if self.options.maxdevices:
350 if self.scanned >= self.options.maxdevices:
351 self.log.info("Limit of %d devices reached" %
352 self.options.maxdevices)
353 return succeed(None)
354
355 def inner(driver):
356 """
357 Twisted driver class to iterate through devices
358
359 @param driver: Zenoss driver
360 @type driver: Zenoss driver
361 @return: successful result is a list of IPs that were added
362 @rtype: Twisted deferred
363 @todo: modularize this function (130+ lines is ridiculous)
364 """
365 try:
366 kw = dict(deviceName=ip,
367 discoverProto=None,
368 devicePath=devicepath,
369 performanceMonitor=self.options.monitor)
370
371
372 if self.options.job:
373 yield self.config().callRemote('getJobProperties',
374 self.options.job)
375 job_props = driver.next()
376 if job_props is not None:
377 kw['zProperties'] = job_props.get('zProperties', {})
378
379 snmpDeviceInfo = None
380
381
382 if not self.options.nosnmp:
383 self.log.debug("Scanning device with address %s", ip)
384 snmpCommunities = kw.get('zProperties', {}).get(
385 'zSnmpCommunities', None)
386 yield self.findRemoteDeviceInfo(ip, devicepath,
387 snmpCommunities)
388 snmpDeviceInfo = driver.next()
389 if snmpDeviceInfo:
390 keys = ('zSnmpCommunity', 'zSnmpPort', 'zSnmpVer',
391 'deviceName')
392 snmpDeviceInfo = dict(zip(keys, snmpDeviceInfo))
393 for k, v in snmpDeviceInfo.iteritems():
394
395 if v: kw[k] = v
396
397
398
399 elif self.options.zSnmpStrictDiscovery:
400 self.log.info('zSnmpStrictDiscovery is True. ' +
401 'Not creating device for %s.'
402 % ip )
403 return
404
405
406
407
408
409
410
411
412
413
414
415 if self.options.zPreferSnmpNaming and \
416 not isip( kw['deviceName'] ):
417
418
419 pass
420 elif self.options.device and not isip(self.options.device):
421 kw['deviceName'] = self.options.device
422 else:
423
424
425 yield asyncNameLookup(ip)
426 try:
427 kw.update(dict(deviceName=driver.next()))
428 except Exception, ex:
429 self.log.debug("Failed to lookup %s (%s)" % (ip, ex))
430
431
432
433 forceDiscovery = bool(self.options.device)
434
435
436
437 yield self.config().callRemote('createDevice', ip,
438 force=forceDiscovery, **kw)
439
440 result = driver.next()
441 if isinstance(result, Failure):
442 raise ZentinelException(result.value)
443 dev, created = result
444
445
446
447
448 if not dev:
449 self.log.info("IP '%s' on no auto-discover, skipping",ip)
450 return
451 else:
452
453 if not created and not dev.temp_device:
454
455
456 if not self.options.remodel:
457 self.log.info("Found IP '%s' on device '%s';"
458 " skipping discovery", ip, dev.id)
459 if self.options.device:
460 self.setExitCode(3)
461 yield succeed(dev)
462 driver.next()
463 return
464 else:
465
466 self.log.info("IP '%s' on device '%s' remodel",
467 ip, dev.id)
468 self.sendDiscoveredEvent(ip, dev)
469
470
471
472 newPath = self.autoAllocate(dev)
473 if newPath:
474 yield self.config().callRemote('moveDevice', dev.id,
475 newPath)
476 driver.next()
477
478
479
480 if not self.options.nosnmp:
481 self.discovered.append(dev.id)
482 yield succeed(dev)
483 driver.next()
484 except ZentinelException, e:
485 self.log.exception(e)
486 evt = dict(device=ip,
487 component=ip,
488 ipAddress=ip,
489 eventKey=ip,
490 eventClass=Status_Snmp,
491 summary=str(e),
492 severity=Info,
493 agent="Discover")
494 if self.options.snmpMissing:
495 self.sendEvent(evt)
496 except Exception, e:
497 self.log.exception("Failed device discovery for '%s'", ip)
498
499 else:
500 yield self.config().callRemote('succeedDiscovery', dev.id)
501 driver.next()
502
503
504 yield succeed(dev)
505 driver.next()
506
507 self.log.debug("Finished scanning device with address %s", ip)
508
509 return drive(inner)
510
511
513 """
514 Twisted driver class to iterate through networks
515
516 @param driver: Zenoss driver
517 @type driver: Zenoss driver
518 @return: successful result is a list of IPs that were added
519 @rtype: Twisted deferred
520 """
521
522 import types
523
524 if type(self.options.net) in types.StringTypes:
525 self.options.net = [self.options.net]
526
527
528 if isinstance(self.options.net, list) and \
529 self.options.net[0].find(",") > -1:
530 self.options.net = [
531 n.strip() for n in self.options.net[0].split(',')
532 ]
533 count = 0
534 devices = []
535 if not self.options.net:
536 yield self.config().callRemote('getDefaultNetworks')
537 self.options.net = driver.next()
538
539 if not self.options.net:
540 self.log.warning("No networks configured")
541 return
542
543 for net in self.options.net:
544 try:
545 yield self.config().callRemote('getNetworks',
546 net,
547 self.options.subnets)
548 nets = driver.next()
549 if not nets:
550 self.log.warning("No networks found for %s" % (net,))
551 continue
552 yield self.discoverIps(nets)
553 ips = driver.next()
554 devices += ips
555 count += len(ips)
556 except Exception, ex:
557 self.log.exception("Error performing net discovery on %s", ex)
558 def discoverDevice(ip):
559 """
560 Discover a particular device
561 NB: Wrapper around self.discoverDevice()
562
563 @param ip: IP address
564 @type ip: string
565 @return: Twisted/Zenoss Python iterable
566 @rtype: Python iterable
567 """
568 return self.discoverDevice(ip,
569 self.options.deviceclass,
570 self.options.productionState)
571 yield NJobs(self.options.parallel, discoverDevice, devices).start()
572 yield succeed("Discovered %d devices" % count)
573 driver.next()
574
575
577 """
578 Display the results that we've obtained
579
580 @param results: what we've discovered
581 @type results: string
582 """
583 if isinstance(results, Failure):
584 self.log.error("Error: %s", results)
585 else:
586 self.log.info("Result: %s", results)
587 self.main()
588
589
591 """
592 Add a device to the system by name or IP.
593
594 @param driver: driver object
595 @type driver: Twisted/Zenoss object
596 @return: Twisted deferred
597 @rtype: Twisted deferred
598 """
599 deviceName = self.options.device
600 self.log.info("Looking for %s" % deviceName)
601 ip = None
602 if isip(deviceName):
603 ip = deviceName
604 else:
605 try:
606
607
608 ip = socket.gethostbyname(deviceName)
609 except socket.error:
610 ip = ""
611 if not ip:
612 raise NoIPAddress("No IP found for name %s" % deviceName)
613 else:
614 self.log.debug("Found IP %s for device %s" % (ip, deviceName))
615 yield self.config().callRemote('getDeviceConfig', [deviceName])
616 me, = driver.next() or [None]
617 if not me or me.temp_device or self.options.remodel:
618 yield self.discoverDevice(ip,
619 devicepath=self.options.deviceclass,
620 prodState=self.options.productionState)
621 yield succeed("Discovered device %s." % deviceName)
622 driver.next()
623
624
626 """
627 Python iterable to go through discovery
628
629 @return: Twisted deferred
630 @rtype: Twisted deferred
631 """
632 myname = socket.getfqdn()
633 self.log.debug("My hostname = %s", myname)
634 myip = None
635 try:
636 myip = socket.gethostbyname(myname)
637 self.log.debug("My IP address = %s", myip)
638 except (socket.error, DNSNameError):
639 raise SystemExit("Failed lookup of my IP for name %s", myname)
640
641 yield self.config().callRemote('getDeviceConfig', [myname])
642 me, = driver.next() or [None]
643 if not me or self.options.remodel:
644 yield self.discoverDevice(myip,
645 devicepath=self.options.deviceclass,
646 prodState=self.options.productionState)
647 me = driver.next()
648 if not me:
649 raise SystemExit("SNMP discover of self '%s' failed" % myname)
650 if not myip:
651 myip = me.manageIp
652 if not myip:
653 raise SystemExit("Can't find my IP for name %s" % myname)
654
655 yield self.discoverRouters(me, [myip])
656
657 driver.next()
658 if self.options.routersonly:
659 self.log.info("Only routers discovered, skipping ping sweep.")
660 else:
661 yield self.config().callRemote('getSubNetworks')
662 yield self.discoverIps(driver.next())
663 ips = driver.next()
664 if not self.options.nosnmp:
665 yield self.discoverDevices(ips)
666 driver.next()
667
668
670 """
671 Our device list comes from our list of newly discovered devices
672
673 @return: list of discovered devices
674 @rtype: Twisted succeed() object
675 """
676 return succeed(self.discovered)
677
678
696
697
699 """
700 Execute a script that will auto allocate devices into their
701 Device Classes
702
703 @param device: device object
704 @type device: device object
705 @return: Device class path to put the new device
706 @rtype: string
707 @todo: make it actually work
708 """
709 self.log.debug("trying to auto-allocate device %s" % device.id )
710 if not device:
711 return
712 script = getattr(device, "zAutoAllocateScript", None)
713 self.log.debug("no auto-allocation script found")
714 if script:
715 import string
716 script = string.join(script, "\n")
717 self.log.debug("using script\n%s" % script)
718 try:
719 compile(script, "zAutoAllocateScript", "exec")
720 except:
721 self.log.error("zAutoAllocateScript contains error")
722 return
723 vars = {'dev': device, 'log': self.log}
724 try:
725 exec(script, vars)
726 except:
727 self.log.error(
728 "error executing zAutoAllocateScript:\n%s" % script)
729 return vars.get('devicePath', None)
730 return
731
732
734 """
735 Command-line option builder for optparse
736 """
737 ZenModeler.buildOptions(self)
738 self.parser.add_option('--net', dest='net', action="append",
739 help="Discover all device on this network")
740 self.parser.add_option('--range', dest='range', action='append',
741 help="Discover all IPs in this range")
742 self.parser.add_option('--deviceclass', dest='deviceclass',
743 default="/Discovered",
744 help="Default device class for discovered devices")
745 self.parser.add_option('--prod_state', dest='productionState',
746 default=1000,
747 help="Initial production state for discovered devices")
748 self.parser.add_option('--remodel', dest='remodel',
749 action="store_true", default=False,
750 help="Remodel existing objects")
751 self.parser.add_option('--routers', dest='routersonly',
752 action="store_true", default=False,
753 help="Only discover routers")
754 self.parser.add_option('--tries', dest='tries', default=1, type="int",
755 help="How many ping tries")
756 self.parser.add_option('--timeout', dest='timeout',
757 default=2, type="float",
758 help="ping timeout in seconds")
759 self.parser.add_option('--chunk', dest='chunkSize',
760 default=10, type="int",
761 help="number of in flight ping packets")
762 self.parser.add_option('--snmp-missing', dest='snmpMissing',
763 action="store_true", default=False,
764 help="Send an event if SNMP is not found on the device")
765 self.parser.add_option('--add-inactive', dest='addInactive',
766 action="store_true", default=False,
767 help="Add all IPs found, even if they are unresponsive")
768 self.parser.add_option('--reset-ptr', dest='resetPtr',
769 action="store_true", default=False,
770 help="Reset all ip PTR records")
771 self.parser.add_option('--no-snmp', dest='nosnmp',
772 action="store_true", default=False,
773 help="Skip SNMP discovery on found IP addresses")
774 self.parser.add_option('--subnets', dest='subnets',
775 action="store_true", default=False,
776 help="Recurse into subnets for discovery")
777 self.parser.add_option('--useFileDescriptor',
778 dest='useFileDescriptor', default=None,
779 help="Use the given (privileged) file descriptor for ping")
780 self.parser.add_option('--assign-devclass-script', dest='autoAllocate',
781 action="store_true", default=False,
782 help="have zendisc auto allocate devices after discovery")
783 self.parser.add_option('--walk', dest='walk', action='store_true',
784 default=False,
785 help="Walk the route tree, performing discovery on all networks")
786 self.parser.add_option('--max-devices', dest='maxdevices',
787 default=0,
788 type='int',
789 help="Collect a maximum number of devices. Default is no limit.")
790 self.parser.add_option('--snmp-strict-discovery',
791 dest='zSnmpStrictDiscovery',
792 action="store_true", default=False,
793 help="Only add devices that can be modeled via snmp." )
794 self.parser.add_option('--prefer-snmp-naming',
795 dest='zPreferSnmpNaming',
796 action="store_true", default=False,
797 help="Prefer snmp name to dns name when modeling via snmp." )
798
799
800 self.parser.add_option('--job', dest='job', help=SUPPRESS_HELP )
801
802
803
804 if __name__ == "__main__":
805 d = ZenDisc()
806 d.processOptions()
807 reactor.run = d.reactorLoop
808 d.run()
809