Esempio n. 1
0
    def __init__(self, single=True ):
        """
        Initalizer

        @param single: collect from a single device?
        @type single: boolean
        """
        ZenModeler.__init__(self, single )
        self.discovered = []

        # pyraw inserts IPV4_SOCKET and IPV6_SOCKET globals
        if IPV4_SOCKET is None:
            self._pinger4 = None
        else:
            protocol = Ping4(IPV4_SOCKET)
            self._pinger4 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)

        if IPV6_SOCKET is None:
            self._pinger6 = None
        else:
            protocol = Ping6(IPV6_SOCKET)
            self._pinger6 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)
Esempio n. 2
0
    def __init__(self, single=True):
        """
        Initalizer

        @param single: collect from a single device?
        @type single: boolean
        """
        ZenModeler.__init__(self, single)
        self.discovered = []

        # pyraw inserts IPV4_SOCKET and IPV6_SOCKET globals
        if IPV4_SOCKET is None:
            self._pinger4 = None
        else:
            protocol = Ping4(IPV4_SOCKET)
            self._pinger4 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)

        if IPV6_SOCKET is None:
            self._pinger6 = None
        else:
            protocol = Ping6(IPV6_SOCKET)
            self._pinger6 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)
Esempio n. 3
0
class ZenDisc(ZenModeler):
    """
    Scan networks and routes looking for devices to add to the ZODB
    """

    initialServices = PBDaemon.initialServices + ['DiscoverService']
    name = 'zendisc'
    scanned = 0

    def __init__(self, single=True ):
        """
        Initalizer

        @param single: collect from a single device?
        @type single: boolean
        """
        ZenModeler.__init__(self, single )
        self.discovered = []

        # pyraw inserts IPV4_SOCKET and IPV6_SOCKET globals
        if IPV4_SOCKET is None:
            self._pinger4 = None
        else:
            protocol = Ping4(IPV4_SOCKET)
            self._pinger4 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)

        if IPV6_SOCKET is None:
            self._pinger6 = None
        else:
            protocol = Ping6(IPV6_SOCKET)
            self._pinger6 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)

    def ping(self, ip):
        """
        Given an IP address, return a deferred that pings the address.
        """
        self.log.debug("Using ipaddr module to convert %s" % ip)
        ipObj = IPAddress(ip)

        if ipObj.version == 6:
            if self._pinger6 is None:
                retval = Failure()
            else:
                retval = self._pinger6.ping(ip)
        else:
            if self._pinger4 is None:
                retval = Failure()
            else:
                retval = self._pinger4.ping(ip)

        return retval

    def config(self):
        """
        Get the DiscoverService

        @return: a DiscoverService from zenhub
        @rtype: function
        """
        return self.services.get('DiscoverService', FakeRemote())


    def discoverIps(self, nets):
        """
        Ping all ips, create entries in the network if necessary.

        @param nets: list of networks to discover
        @type nets: list
        @return: successful result is a list of IPs that were added
        @rtype: Twisted deferred
        """
        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            """
            ips = []
            goodCount = 0
            # it would be nice to interleave ping/discover
            for net in nets:
                if self.options.subnets and len(net.children()) > 0:
                    continue
                if not getattr(net, "zAutoDiscover", False):
                    self.log.info(
                        "Skipping network %s because zAutoDiscover is False"
                        % net.getNetworkName())
                    continue
                self.log.info("Discover network '%s'", net.getNetworkName())
                yield NJobs(self.options.chunkSize,
                            self.ping,
                            net.fullIpList()).start()
                results = driver.next()
                goodips = [
                    v.ipaddr for v in results if not isinstance(v, Failure)]
                badips = [
                    v.value.ipaddr for v in results if isinstance(v, Failure)]
                goodCount += len(goodips)
                self.log.debug("Got %d good IPs and %d bad IPs",
                               len(goodips), len(badips))
                yield self.config().callRemote('pingStatus',
                                               net,
                                               goodips,
                                               badips,
                                               self.options.resetPtr,
                                               self.options.addInactive)
                ips += driver.next()
                self.log.info("Discovered %s active ips", goodCount)
            # make sure this is the return result for the driver
            yield succeed(ips)
            driver.next()

        d = drive(inner)
        return d

    def discoverRanges(self, driver):
        """
        Ping all IPs in the range and create devices for the ones that come
        back.

        @param ranges: list of ranges to discover
        @type ranges: list
        """
        if isinstance(self.options.range, basestring):
            self.options.range = [self.options.range]
        # in case someone uses 10.0.0.0-5,192.168.0.1-5 instead of
        # --range 10.0.0.0-5 --range 192.168.0.1-5
        if (isinstance(self.options.range, list) and
            self.options.range[0].find(",") > -1):
            self.options.range = [n.strip() for n in
                                  self.options.range[0].split(',')]
        ips = []
        goodCount = 0
        for iprange in self.options.range:
            # Parse to find ips included
            ips.extend(parse_iprange(iprange))
        yield NJobs(self.options.chunkSize,
                    self.ping,
                    ips).start()
        results = driver.next()
        goodips = [v.ipaddr for v in results if not isinstance(v, Failure)]
        badips = [v.value.ipaddr for v in results if isinstance(v, Failure)]
        goodCount += len(goodips)
        self.log.debug("Got %d good IPs and %d bad IPs",
                       len(goodips), len(badips))
        yield self.discoverDevices(goodips)
        yield succeed("Discovered %d active IPs" % goodCount)
        driver.next()


    def discoverRouters(self, rootdev, seenips=None):
        """
        Discover all default routers based on DMD configuration.

        @param rootdev: device root in DMD
        @type rootdev: device class
        @param seenips: list of IP addresses
        @type seenips: list of strings
        @return: Twisted/Zenoss Python iterable
        @rtype: Python iterable
        """
        if not seenips:
            seenips = []

        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            """
            yield self.config().callRemote('followNextHopIps', rootdev.id)
            for ip in driver.next():
                if ip in seenips:
                    continue
                self.log.info("device '%s' next hop '%s'", rootdev.id, ip)
                seenips.append(ip)
                yield self.discoverDevice(ip, devicepath="/Network/Router")
                router = driver.next()
                if not router:
                    continue
                yield self.discoverRouters(router, seenips)
                driver.next()

        return drive(inner)


    def sendDiscoveredEvent(self, ip, dev=None, sev=2):
        """
        Send a 'device discovered' event through zenhub

        @param ip: IP addresses
        @type ip: strings
        @param dev: remote device name
        @type dev: device object
        @param sev: severity
        @type sev: integer
        """
        devname = comp = ip
        if dev:
            devname = dev.id
        msg = "'Discovered device name '%s' for ip '%s'" % (devname, ip)
        evt = dict(device=devname,ipAddress=ip,eventKey=ip,
                   component=comp,eventClass=Status_Snmp,
                   summary=msg, severity=sev,
                   agent="Discover")
        self.sendEvent(evt)


    def discoverDevices(self,
                        ips,
                        devicepath="/Discovered",
                        prodState=1000):
        """
        Discover devices by active ips that are not associated with a device.

        @param ips: list of IP addresses
        @type ips: list of strings
        @param devicepath: where in the DMD to put any discovered devices
        @type devicepath: string
        @param prodState: production state (see Admin Guide for a description)
        @type prodState: integer
        @return: Twisted/Zenoss Python iterable
        @rtype: Python iterable
        """
        def discoverDevice(ip):
            """
            Discover a particular device
            NB: Wrapper around self.discoverDevice()

            @param ip: IP address
            @type ip: string
            @return: Twisted/Zenoss Python iterable
            @rtype: Python iterable
            """
            return self.discoverDevice(ip, devicepath, prodState)

        return NJobs(self.options.parallel, discoverDevice, ips).start()


    def findRemoteDeviceInfo(self, ip, devicePath, deviceSnmpCommunities=None):
        """
        Scan a device for ways of naming it: PTR DNS record or a SNMP name

        @param ip: IP address
        @type ip: string
        @param devicePath: where in the DMD to put any discovered devices
        @type devicePath: string
        @param deviceSnmpCommunities: Optional list of SNMP community strings
            to try, overriding those set on the device class
        @type deviceSnmpCommunities: list
        @return: result is None or a tuple containing
            (community, port, version, snmp name)
        @rtype: deferred: Twisted deferred
        """
        from pynetsnmp.twistedsnmp import AgentProxy

        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            """
            self.log.debug("findRemoteDeviceInfo.inner: Doing SNMP lookup on device %s", ip)
            yield self.config().callRemote('getSnmpConfig', devicePath)
            communities, ports, version, timeout, retries = driver.next()
            self.log.debug("findRemoteDeviceInfo.inner: override acquired community strings")
            # Override the device class communities with the ones set on
            # this device, if they exist
            if deviceSnmpCommunities is not None:
                communities = deviceSnmpCommunities

            # Reverse the communities so that ones earlier in the list have a
            # higher weight.
            communities.reverse()

            configs = []
            for i, community in enumerate(communities):
                for port in ports:
                    port = int(port)
                    configs.append(SnmpV1Config(
                        ip, weight=i, port=port, timeout=timeout,
                        retries=retries, community=community))
                    configs.append(SnmpV2cConfig(
                        ip, weight=i+100, port=port, timeout=timeout,
                        retries=retries, community=community))

            yield SnmpAgentDiscoverer().findBestConfig(configs)
            driver.next()
            self.log.debug("Finished SNMP lookup on device %s", ip)

        return drive(inner)


    def discoverDevice(self, ip, devicepath="/Discovered", prodState=1000):
        """
        Discover a device based on its IP address.

        @param ip: IP address
        @type ip: string
        @param devicepath: where in the DMD to put any discovered devices
        @type devicepath: string
        @param prodState: production state (see Admin Guide for a description)
        @type prodState: integer
        @return: Twisted/Zenoss Python iterable
        @rtype: Python iterable
        """
        self.scanned += 1
        if self.options.maxdevices:
            if self.scanned >= self.options.maxdevices:
                self.log.info("Limit of %d devices reached" %
                              self.options.maxdevices)
                return succeed(None)

        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            @todo: modularize this function (130+ lines is ridiculous)
            """
            try:
                kw = dict(deviceName=ip,
                          discoverProto=None,
                          devicePath=devicepath,
                          performanceMonitor=self.options.monitor,
                          productionState=prodState)

                # If zProperties are set via a job, get them and pass them in
                if self.options.job:
                    yield self.config().callRemote('getJobProperties',
                                                   self.options.job)
                    job_props = driver.next()
                    if job_props is not None:
                        # grab zProperties from Job
                        kw['zProperties'] = getattr(job_props, 'zProperties', {})
                        # grab other Device properties from jobs
                        #deviceProps = job_props.get('deviceProps', {})
                        #kw.update(deviceProps)
                        #@FIXME we are not getting deviceProps, check calling
                        # chain for clues. twisted upgrade heartburn perhaps?

                # if we are using SNMP, lookup the device SNMP info and use the
                # name defined there for deviceName
                if not self.options.nosnmp:
                    self.log.debug("Scanning device with address %s", ip)
                    snmpCommunities = kw.get('zProperties', {}).get(
                        'zSnmpCommunities', None)
                    yield self.findRemoteDeviceInfo(ip, devicepath,
                                                    snmpCommunities)
                    snmp_config = driver.next()
                    if snmp_config:
                        if snmp_config.sysName:
                            kw['deviceName'] = snmp_config.sysName

                        if snmp_config.version:
                            kw['zSnmpVer'] = snmp_config.version

                        if snmp_config.port:
                            kw['zSnmpPort'] = snmp_config.port

                        if snmp_config.community:
                            kw['zSnmpCommunity'] = snmp_config.community

                    # if we are using SNMP, did not find any snmp info,
                    # and we are in strict discovery mode, do not
                    # create a device
                    elif self.options.zSnmpStrictDiscovery:
                        self.log.info('zSnmpStrictDiscovery is True.  ' +
                                      'Not creating device for %s.'
                                      % ip )
                        return

                # RULES FOR DEVICE NAMING:
                # 1. If zPreferSnmpNaming is true:
                #        If snmp name is returned, use snmp name. Otherwise,
                #        use the passed device name.  If no device name was passed,
                #        do a dns lookup on the ip.
                # 2. If zPreferSnmpNaming is false:
                #        If we are discovering a single device and a name is
                #        passed in instead of an IP, use the passed-in name.
                #        Otherwise, do a dns lookup on the ip.
                if self.options.zPreferSnmpNaming and \
                   not isip( kw['deviceName'] ):
                    # In this case, we want to keep kw['deviceName'] as-is,
                    # because it is what we got from snmp
                    pass
                elif self.options.device and not isip(self.options.device):
                    kw['deviceName'] = self.options.device
                else:
                    # An IP was passed in so we do a reverse lookup on it to get
                    # deviceName
                    yield asyncNameLookup(ip)
                    try:
                        kw.update(dict(deviceName=driver.next()))
                    except Exception, ex:
                        self.log.debug("Failed to lookup %s (%s)" % (ip, ex))

                # If it's discovering a particular device,
                # ignore zAutoDiscover limitations
                forceDiscovery = bool(self.options.device)


                # now create the device by calling zenhub
                yield self.config().callRemote('createDevice', ipunwrap(ip),
                                   force=forceDiscovery, **kw)

                result = driver.next()
                self.log.debug("ZenDisc.discoverDevice.inner: got result from remote_createDevice")
                if isinstance(result, Failure):
                    raise ZentinelException(result.value)
                dev, created = result

                # if no device came back from createDevice we assume that it
                # was told to not auto-discover the device.  This seems very
                # dubious to me! -EAD
                if not dev:
                    self.log.info("IP '%s' on no auto-discover, skipping",ip)
                    return
                else:
                    # A device came back and it already existed.
                    if not created and not dev.temp_device:
                        # if we shouldn't remodel skip the device by returning
                        # at the end of this block
                        if not self.options.remodel:
                            self.log.info("Found IP '%s' on device '%s';"
                                          " skipping discovery", ip, dev.id)
                            if self.options.device:
                                self.setExitCode(3)
                            yield succeed(dev)
                            driver.next()
                            return
                        else:
                        # we continue on to model the device.
                            self.log.info("IP '%s' on device '%s' remodel",
                                          ip, dev.id)
                    self.sendDiscoveredEvent(ip, dev)

                # the device that we found/created or that should be remodeled
                # is added to the list of devices to be modeled later
                if not self.options.nosnmp:
                    self.discovered.append(dev.id)
                yield succeed(dev)
                driver.next()
            except ZentinelException, e:
                self.log.exception(e)
                evt = dict(device=ip,
                           component=ip,
                           ipAddress=ip,
                           eventKey=ip,
                           eventClass=Status_Snmp,
                           summary=str(e),
                           severity=Info,
                           agent="Discover")
                if self.options.snmpMissing:
                    self.sendEvent(evt)
            except Exception, e:
                self.log.exception("Failed device discovery for '%s'", ip)
Esempio n. 4
0
class ZenDisc(ZenModeler):
    """
    Scan networks and routes looking for devices to add to the ZODB
    """

    initialServices = PBDaemon.initialServices + ['DiscoverService']
    name = 'zendisc'
    scanned = 0

    def __init__(self, single=True):
        """
        Initalizer

        @param single: collect from a single device?
        @type single: boolean
        """
        ZenModeler.__init__(self, single)
        self.discovered = []

        # pyraw inserts IPV4_SOCKET and IPV6_SOCKET globals
        if IPV4_SOCKET is None:
            self._pinger4 = None
        else:
            protocol = Ping4(IPV4_SOCKET)
            self._pinger4 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)

        if IPV6_SOCKET is None:
            self._pinger6 = None
        else:
            protocol = Ping6(IPV6_SOCKET)
            self._pinger6 = PingService(protocol,
                                        timeout=self.options.timeout,
                                        defaultTries=self.options.tries)

    def ping(self, ip):
        """
        Given an IP address, return a deferred that pings the address.
        """
        self.log.debug("Using ipaddr module to convert %s" % ip)
        ipObj = IPAddress(ip)

        if ipObj.version == 6:
            if self._pinger6 is None:
                retval = Failure()
            else:
                retval = self._pinger6.ping(ip)
        else:
            if self._pinger4 is None:
                retval = Failure()
            else:
                retval = self._pinger4.ping(ip)

        return retval

    def config(self):
        """
        Get the DiscoverService

        @return: a DiscoverService from zenhub
        @rtype: function
        """
        return self.services.get('DiscoverService', FakeRemote())

    def discoverIps(self, nets):
        """
        Ping all ips, create entries in the network if necessary.

        @param nets: list of networks to discover
        @type nets: list
        @return: successful result is a list of IPs that were added
        @rtype: Twisted deferred
        """
        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            """
            ips = []
            goodCount = 0
            # it would be nice to interleave ping/discover
            for net in nets:
                if self.options.subnets and len(net.children()) > 0:
                    continue
                if not getattr(net, "zAutoDiscover", False):
                    self.log.info(
                        "Skipping network %s because zAutoDiscover is False" %
                        net.getNetworkName())
                    continue
                self.log.info("Discover network '%s'", net.getNetworkName())
                yield NJobs(self.options.chunkSize, self.ping,
                            net.fullIpList()).start()
                results = driver.next()
                goodips = [
                    v.ipaddr for v in results if not isinstance(v, Failure)
                ]
                badips = [
                    v.value.ipaddr for v in results if isinstance(v, Failure)
                ]
                goodCount += len(goodips)
                self.log.debug("Got %d good IPs and %d bad IPs", len(goodips),
                               len(badips))
                yield self.config().callRemote('pingStatus', net, goodips,
                                               badips, self.options.resetPtr,
                                               self.options.addInactive)
                ips += driver.next()
                self.log.info("Discovered %s active ips", goodCount)
            # make sure this is the return result for the driver
            yield succeed(ips)
            driver.next()

        d = drive(inner)
        return d

    def discoverRanges(self, driver):
        """
        Ping all IPs in the range and create devices for the ones that come
        back.

        @param ranges: list of ranges to discover
        @type ranges: list
        """
        if isinstance(self.options.range, basestring):
            self.options.range = [self.options.range]
        # in case someone uses 10.0.0.0-5,192.168.0.1-5 instead of
        # --range 10.0.0.0-5 --range 192.168.0.1-5
        if (isinstance(self.options.range, list)
                and self.options.range[0].find(",") > -1):
            self.options.range = [
                n.strip() for n in self.options.range[0].split(',')
            ]
        ips = []
        goodCount = 0
        for iprange in self.options.range:
            # Parse to find ips included
            ips.extend(parse_iprange(iprange))
        yield NJobs(self.options.chunkSize, self.ping, ips).start()
        results = driver.next()
        goodips = [v.ipaddr for v in results if not isinstance(v, Failure)]
        badips = [v.value.ipaddr for v in results if isinstance(v, Failure)]
        goodCount += len(goodips)
        self.log.debug("Got %d good IPs and %d bad IPs", len(goodips),
                       len(badips))
        yield self.discoverDevices(goodips)
        yield succeed("Discovered %d active IPs" % goodCount)
        driver.next()

    def discoverRouters(self, rootdev, seenips=None):
        """
        Discover all default routers based on DMD configuration.

        @param rootdev: device root in DMD
        @type rootdev: device class
        @param seenips: list of IP addresses
        @type seenips: list of strings
        @return: Twisted/Zenoss Python iterable
        @rtype: Python iterable
        """
        if not seenips:
            seenips = []

        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            """
            yield self.config().callRemote('followNextHopIps', rootdev.id)
            for ip in driver.next():
                if ip in seenips:
                    continue
                self.log.info("device '%s' next hop '%s'", rootdev.id, ip)
                seenips.append(ip)
                yield self.discoverDevice(ip, devicepath="/Network/Router")
                router = driver.next()
                if not router:
                    continue
                yield self.discoverRouters(router, seenips)
                driver.next()

        return drive(inner)

    def sendDiscoveredEvent(self, ip, dev=None, sev=2):
        """
        Send a 'device discovered' event through zenhub

        @param ip: IP addresses
        @type ip: strings
        @param dev: remote device name
        @type dev: device object
        @param sev: severity
        @type sev: integer
        """
        devname = comp = ip
        if dev:
            devname = dev.id
        msg = "'Discovered device name '%s' for ip '%s'" % (devname, ip)
        evt = dict(device=devname,
                   ipAddress=ip,
                   eventKey=ip,
                   component=comp,
                   eventClass=Status_Snmp,
                   summary=msg,
                   severity=sev,
                   agent="Discover")
        self.sendEvent(evt)

    def discoverDevices(self, ips, devicepath="/Discovered", prodState=1000):
        """
        Discover devices by active ips that are not associated with a device.

        @param ips: list of IP addresses
        @type ips: list of strings
        @param devicepath: where in the DMD to put any discovered devices
        @type devicepath: string
        @param prodState: production state (see Admin Guide for a description)
        @type prodState: integer
        @return: Twisted/Zenoss Python iterable
        @rtype: Python iterable
        """
        def discoverDevice(ip):
            """
            Discover a particular device
            NB: Wrapper around self.discoverDevice()

            @param ip: IP address
            @type ip: string
            @return: Twisted/Zenoss Python iterable
            @rtype: Python iterable
            """
            return self.discoverDevice(ip, devicepath, prodState)

        return NJobs(self.options.parallel, discoverDevice, ips).start()

    def findRemoteDeviceInfo(self, ip, devicePath, deviceSnmpCommunities=None):
        """
        Scan a device for ways of naming it: PTR DNS record or a SNMP name

        @param ip: IP address
        @type ip: string
        @param devicePath: where in the DMD to put any discovered devices
        @type devicePath: string
        @param deviceSnmpCommunities: Optional list of SNMP community strings
            to try, overriding those set on the device class
        @type deviceSnmpCommunities: list
        @return: result is None or a tuple containing
            (community, port, version, snmp name)
        @rtype: deferred: Twisted deferred
        """
        from pynetsnmp.twistedsnmp import AgentProxy

        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            """
            self.log.debug(
                "findRemoteDeviceInfo.inner: Doing SNMP lookup on device %s",
                ip)
            yield self.config().callRemote('getSnmpConfig', devicePath)
            communities, ports, version, timeout, retries = driver.next()
            self.log.debug(
                "findRemoteDeviceInfo.inner: override acquired community strings"
            )
            # Override the device class communities with the ones set on
            # this device, if they exist
            if deviceSnmpCommunities is not None:
                communities = deviceSnmpCommunities

            # Reverse the communities so that ones earlier in the list have a
            # higher weight.
            communities.reverse()

            configs = []
            for i, community in enumerate(communities):
                for port in ports:
                    port = int(port)
                    configs.append(
                        SnmpV1Config(ip,
                                     weight=i,
                                     port=port,
                                     timeout=timeout,
                                     retries=retries,
                                     community=community))
                    configs.append(
                        SnmpV2cConfig(ip,
                                      weight=i + 100,
                                      port=port,
                                      timeout=timeout,
                                      retries=retries,
                                      community=community))

            yield SnmpAgentDiscoverer().findBestConfig(configs)
            driver.next()
            self.log.debug("Finished SNMP lookup on device %s", ip)

        return drive(inner)

    def discoverDevice(self, ip, devicepath="/Discovered", prodState=1000):
        """
        Discover a device based on its IP address.

        @param ip: IP address
        @type ip: string
        @param devicepath: where in the DMD to put any discovered devices
        @type devicepath: string
        @param prodState: production state (see Admin Guide for a description)
        @type prodState: integer
        @return: Twisted/Zenoss Python iterable
        @rtype: Python iterable
        """
        self.scanned += 1
        if self.options.maxdevices:
            if self.scanned >= self.options.maxdevices:
                self.log.info("Limit of %d devices reached" %
                              self.options.maxdevices)
                return succeed(None)

        def inner(driver):
            """
            Twisted driver class to iterate through devices

            @param driver: Zenoss driver
            @type driver: Zenoss driver
            @return: successful result is a list of IPs that were added
            @rtype: Twisted deferred
            @todo: modularize this function (130+ lines is ridiculous)
            """
            try:
                kw = dict(deviceName=ip,
                          discoverProto=None,
                          devicePath=devicepath,
                          performanceMonitor=self.options.monitor,
                          productionState=prodState)

                # If zProperties are set via a job, get them and pass them in
                if self.options.job:
                    yield self.config().callRemote('getJobProperties',
                                                   self.options.job)
                    job_props = driver.next()
                    if job_props is not None:
                        # grab zProperties from Job
                        kw['zProperties'] = getattr(job_props, 'zProperties',
                                                    {})
                        # grab other Device properties from jobs
                        #deviceProps = job_props.get('deviceProps', {})
                        #kw.update(deviceProps)
                        #@FIXME we are not getting deviceProps, check calling
                        # chain for clues. twisted upgrade heartburn perhaps?

                # if we are using SNMP, lookup the device SNMP info and use the
                # name defined there for deviceName
                if not self.options.nosnmp:
                    self.log.debug("Scanning device with address %s", ip)
                    snmpCommunities = kw.get('zProperties',
                                             {}).get('zSnmpCommunities', None)
                    yield self.findRemoteDeviceInfo(ip, devicepath,
                                                    snmpCommunities)
                    snmp_config = driver.next()
                    if snmp_config:
                        if snmp_config.sysName:
                            kw['deviceName'] = snmp_config.sysName

                        if snmp_config.version:
                            kw['zSnmpVer'] = snmp_config.version

                        if snmp_config.port:
                            kw['zSnmpPort'] = snmp_config.port

                        if snmp_config.community:
                            kw['zSnmpCommunity'] = snmp_config.community

                    # if we are using SNMP, did not find any snmp info,
                    # and we are in strict discovery mode, do not
                    # create a device
                    elif self.options.zSnmpStrictDiscovery:
                        self.log.info('zSnmpStrictDiscovery is True.  ' +
                                      'Not creating device for %s.' % ip)
                        return

                # RULES FOR DEVICE NAMING:
                # 1. If zPreferSnmpNaming is true:
                #        If snmp name is returned, use snmp name. Otherwise,
                #        use the passed device name.  If no device name was passed,
                #        do a dns lookup on the ip.
                # 2. If zPreferSnmpNaming is false:
                #        If we are discovering a single device and a name is
                #        passed in instead of an IP, use the passed-in name.
                #        Otherwise, do a dns lookup on the ip.
                if self.options.zPreferSnmpNaming and \
                   not isip( kw['deviceName'] ):
                    # In this case, we want to keep kw['deviceName'] as-is,
                    # because it is what we got from snmp
                    pass
                elif self.options.device and not isip(self.options.device):
                    kw['deviceName'] = self.options.device
                else:
                    # An IP was passed in so we do a reverse lookup on it to get
                    # deviceName
                    yield asyncNameLookup(ip)
                    try:
                        kw.update(dict(deviceName=driver.next()))
                    except Exception, ex:
                        self.log.debug("Failed to lookup %s (%s)" % (ip, ex))

                # If it's discovering a particular device,
                # ignore zAutoDiscover limitations
                forceDiscovery = bool(self.options.device)

                # now create the device by calling zenhub
                yield self.config().callRemote('createDevice',
                                               ipunwrap(ip),
                                               force=forceDiscovery,
                                               **kw)

                result = driver.next()
                self.log.debug(
                    "ZenDisc.discoverDevice.inner: got result from remote_createDevice"
                )
                if isinstance(result, Failure):
                    raise ZentinelException(result.value)
                dev, created = result

                # if no device came back from createDevice we assume that it
                # was told to not auto-discover the device.  This seems very
                # dubious to me! -EAD
                if not dev:
                    self.log.info("IP '%s' on no auto-discover, skipping", ip)
                    return
                else:
                    # A device came back and it already existed.
                    if not created and not dev.temp_device:
                        # if we shouldn't remodel skip the device by returning
                        # at the end of this block
                        if not self.options.remodel:
                            self.log.info(
                                "Found IP '%s' on device '%s';"
                                " skipping discovery", ip, dev.id)
                            if self.options.device:
                                self.setExitCode(3)
                            yield succeed(dev)
                            driver.next()
                            return
                        else:
                            # we continue on to model the device.
                            self.log.info("IP '%s' on device '%s' remodel", ip,
                                          dev.id)
                    self.sendDiscoveredEvent(ip, dev)

                # the device that we found/created or that should be remodeled
                # is added to the list of devices to be modeled later
                if not self.options.nosnmp:
                    self.discovered.append(dev.id)
                yield succeed(dev)
                driver.next()
            except ZentinelException, e:
                self.log.exception(e)
                evt = dict(device=ip,
                           component=ip,
                           ipAddress=ip,
                           eventKey=ip,
                           eventClass=Status_Snmp,
                           summary=str(e),
                           severity=Info,
                           agent="Discover")
                if self.options.snmpMissing:
                    self.sendEvent(evt)
            except Exception, e:
                self.log.exception("Failed device discovery for '%s'", ip)