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_driverfield. Anevpnplan cannot be saved withios. - At run time. A plan that leaves
napalm_driverblank 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:
- Compare the device-reported value with NetBox state.
- Call
_record_entry()to create aFactsReportEntrywith one ofnew,changed,confirmed, orstale. - If
_should_apply()returnsTrue(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.