Skip to content

Collectors Overview

Each CollectionPlan runs exactly one collector. The full set is defined in CollectionTypeChoices (netbox_facts/choices.py):

Type key Display NAPALM call(s) Vendor-specific dispatch?
arp ARP get_arp_table() No (Junos driver enriched)
ndp IPv6 Neighbor Discovery get_ipv6_neighbors_table() No (Junos driver enriched)
inventory Inventory get_facts() + (Junos) get_chassis_inventory() Junos has chassis path
interfaces Interfaces get_interfaces() + get_interfaces_ip() + get_network_instances() Enhanced Junos data unlocks the logical-interfaces path
lldp LLDP get_lldp_neighbors_detail() No
ethernet_switching Ethernet Switching Tables get_mac_address_table() No
l2_circuits L2 Circuits CLI: show l2circuit connections Yes (Junos only)
evpn EVPN CLI: show evpn mac-table Yes (Junos only)
bgp BGP get_bgp_neighbors_detail() No
ospf OSPF CLI: show ospf neighbor Yes (Junos only)

The runner is NapalmCollector in netbox_facts/helpers/collector.py. Each collector method matches the type key (e.g. arp(), ethernet_switching()).

Driver compatibility

The collectors marked "Yes (Junos only)" above reach the device through a vendor-specific implementation rather than a standard NAPALM getter, so they can only run against a driver that has one. That table is encoded in COLLECTOR_SUPPORTED_DRIVERS (netbox_facts/choices.py) and enforced twice:

  • At save time. A plan that names an incompatible driver fails validation on the napalm_driver field. An evpn plan cannot be saved with ios.
  • At run time. A plan that leaves napalm_driver blank resolves a driver per device, so compatibility is not knowable until the run. Each device whose resolved driver has no implementation is skipped with a warning and counted in the run summary; the rest of the scope is still collected. This is how a Junos-only collector behaves on a mixed-vendor scope: it collects from the Junos devices and passes over the others.

A driver named by its plugin-local dotted path (netbox_facts.napalm.junos) is treated as the vendor it enhances (junos) by both checks and by the vendor dispatch itself.

Adding a vendor to one of these collectors means implementing _<collector>_<vendor>() and adding the driver to that collector's row in COLLECTOR_SUPPORTED_DRIVERS. _get_vendor_method() dispatches off that same table, so there is no second list to keep in step.

Detect-only and apply

Every collector follows the same pattern:

  1. Compare the device-reported value with NetBox state.
  2. Call _record_entry() to create a FactsReportEntry with one of new, changed, confirmed, or stale.
  3. If _should_apply() returns True (i.e. detect_only=False), perform the mutation and call _mark_entry_applied().

A second apply path (netbox_facts/helpers/applier.py) re-implements the same handlers in a per-entry-savepoint form so reviewers can selectively apply pending entries from a detect-only report. The dispatch table is APPLY_HANDLERS.

Auto-discovered tag

Objects created by collectors are tagged Automatically Discovered (constant: AUTO_D_TAG, slug automatically-discovered). Stale detection relies on this tag, so manually-added objects are never considered stale.

Reserved object

This tag's row is created by a migration and every ownership check in helpers/collector.py and helpers/applier.py matches it by slug, not by its display name -- so renaming it in the UI does not break anything. Deleting the tag, or changing its slug directly through the REST API, is blocked by a signal with a message naming the plugin; its color and description can still be edited freely. See get_discovery_tag() in netbox_facts/helpers/netbox.py for the lookup and netbox_facts/migrations/0035_discovery_tag.py for how the row is first established (including adopting a pre-existing tag of the same name from before this migration existed).

Stale sweeps and the grace period

Four sweeps reconcile what a device no longer reports:

Sweep Objects What the removal does
_ip_neighbors() (ARP, NDP) Auto-discovered IPs reachable through the device's MACs, filtered to the collector's address family Records a stale entry
_collect_chassis_inventory() Discovered InventoryItems not reported by the chassis Deletes the item
_collect_chassis_inventory() Auto-discovered Modules in bays the chassis did not report Deletes the module
_detect_stale_ips() (interfaces) Auto-discovered IPs on the interfaces this run inspected Unassigns the address

Every one of them hands its absent objects to _past_grace() and acts only on what comes back. With a grace period configured on the plan (or plugin-wide), the first absence writes an OrphanCandidate row recording when the object went missing, tags the object Orphaned (netbox-facts), and holds it back; later runs move the row's last_missing stamp. Only once now - first_missing has reached the period is the object returned for the behavior in the table above.

An object the sweep did not report as absent is one it found again, and _past_grace() forgets it: the row goes and the tag comes off. Driving that pass off the rows rather than off the objects seen is what keeps it cheap -- a device with nothing orphaned costs one query -- and keeping the partition and the forgetting in one place is what stops a sweep from deleting a row the same run has just written.

A grace period of 0 -- the shipped default -- short-circuits the gate before any row is written, so a plan without one behaves exactly as it did before the grace period existed. See Stale grace period.

Interface filter

The plugin-wide valid_interfaces_re setting filters which interfaces a collector iterates. It is compiled once per run and applied to:

  • ARP / NDP entries grouped by interface.
  • Interface entries from get_interfaces().
  • Ethernet switching MAC entries.

Interfaces whose names do not match are silently skipped.

Per-collector pages