Skip to main content

Provision NetBox devices and virtual machines into OpenNMS via the REST provisioning API.

Project description

netbox-opennms-plugin

Provision NetBox devices and virtual machines into OpenNMS Horizon 36 as requisition nodes — NetBox is the source of truth, OpenNMS monitoring is a derived artifact kept in sync from NetBox intent.

CI Release License: MIT NetBox 4.6.1+ Python 3.12+ Docs

📖 Documentation & contributor guide →

NetBox OpenNMS — Requisition detail

Why

NetBox is your source of truth for what exists; OpenNMS (Horizon 36) monitors it. This plugin closes the gap: you describe monitoring intent in NetBox — which Devices/VMs, which detectors and policies, which services — and the plugin renders and imports the matching OpenNMS requisition over the REST provisioning API. Membership is a live NetBox query, so the monitored set follows your inventory automatically, and every re-sync is idempotent (render-and-replace — a node is never duplicated or implicitly moved).

Features

  • Filter-based Requisitions — a Requisition is one user-named OpenNMS Foreign Source whose members are a live NetBox filter (role, tag, site, status, custom field, …) over Devices and/or Virtual Machines.
  • Discovery-driven detectors & policies — configured from your live OpenNMS catalog (classes + parameters read via REST, the same API the OpenNMS UI uses), with a curated preset overlay for labels/defaults and a freeform-class escape hatch.
  • Per-interface SNMP roles — the management IP is the Primary SNMP interface by default; add more IPs as Primary / Secondary / Not-eligible.
  • Asset & metadata enrichment — map NetBox inventory to OpenNMS node asset fields, and attach meta-data at node / interface / service scope.
  • Conflict safety — an object matched by two Requisitions is a blocking conflict; Sync of every involved Requisition freezes until you resolve it, so a node always lives in exactly one Foreign Source.
  • Graded node status — a member with no management IP becomes an inventory-only node with a warning, not a silent skip.
  • Dry-run before sync — a per-node diff of exactly what a Sync would add / remove / change against the live OpenNMS state.
  • Honest background sync — Sync/Remove run as NetBox Jobs; an OpenNMS 202 is reported as submitted for import, never "provisioned"; a drift reconciler cleans up only the requisitions the plugin owns.

Compatibility

NetBox 4.6.1+
Python 3.12+
OpenNMS Horizon 36
License MIT

Quick start (try it in the browser)

Spin up a throwaway NetBox (UI + worker) and a disposable OpenNMS Horizon 36, then click Sync to OpenNMS and watch a node appear:

cd quickstart
docker compose --profile opennms up -d

The quickstart/ stack is seeded with example Devices, VMs, and Requisitions, so every path is clickable from the first boot. Full walkthrough — seeding, dry-run, and Sync — in the Quickstart guide.

Installation

Install into the same Python environment as NetBox. A PyPI release is a fast-follow; until then, install from the tagged Git ref:

pip install "git+https://github.com/no42-org/netbox-opennms-plugin@v0.0.2"

Enable the plugin in NetBox's configuration.py:

PLUGINS = ["netbox_opennms"]

Apply migrations and restart NetBox and its RQ worker:

python manage.py migrate

Then set the OpenNMS connection — see Configuration.

Kubernetes (Helm chart)

The netbox-community/netbox chart enables plugins that are already installed in the image — it has no runtime pip step — so bake the plugin into a custom image, then point the chart at it and enable it. (This is also netbox-docker's own current guidance.) Installing a plugin at runtime by mounting it into the container is not a supported path: netbox-docker declined to add runtime-mounted plugins (PR #1071) in favour of build-time images, because a container should be fully defined at build time and plugins that ship static assets need collectstatic to run during the build. (The plugin itself is pure-Python, so it is not the constraint — this is about the chart and netbox-docker, not the package.)

  1. Build an image with the plugin. Match the base image to a NetBox version the plugin supports (4.6.1+) and to the chart's appVersion:

    # Dockerfile
    FROM netboxcommunity/netbox:v4.6      # NetBox 4.6.1+; keep in step with the chart appVersion
    RUN /opt/netbox/venv/bin/pip install \
        "git+https://github.com/no42-org/netbox-opennms-plugin@v0.0.2"
    

    The tagged Git ref is the install source available today. PyPI is a fast-follow (then this becomes pip install netbox-opennms-plugin==<version>); for an air-gapped build, make build a wheel and COPY it in instead.

    docker build -t registry.example.org/netbox-opennms:v4.6 .
    docker push registry.example.org/netbox-opennms:v4.6
    
  2. In values.yaml, point the chart at that image, enable the plugin, and enable the worker:

    image:
      repository: registry.example.org/netbox-opennms
      tag: v4.6
    
    plugins:
      - netbox_opennms          # the package must already be in the image (step 1)
    
    pluginsConfig:
      netbox_opennms:
        opennms_url: "https://opennms.example.org/opennms"
        opennms_username: "provision-svc"
        opennms_password: "********"     # lab only — in production source from a Secret via extraConfig
        default_location: ""
        import_mode: "false"
        reconcile_orphans: "true"
    
    worker:
      enabled: true
    

    The worker must run the same plugin-bearing image. The plugin's Sync and drift-reconciler jobs run in the RQ worker; the chart's worker uses image by default, so leave it unset. If the worker runs an image without the plugin, the deploy succeeds and the UI works, but background jobs fail at dequeue with an import error.

  3. Install or upgrade. The image entrypoint applies migrations on boot (no separate manage.py migrate), so just confirm the plugin's migrations ran:

    helm repo add netbox https://netbox-community.github.io/netbox-chart/
    helm upgrade --install netbox netbox/netbox -f values.yaml
    kubectl logs deploy/netbox | grep netbox_opennms   # → Applying netbox_opennms... OK
    

Keep opennms_password out of plaintext values — load the plugin config from a Secret via the chart's extraConfig. On upgrades, rebuild the image with the new plugin version, bump image.tag, and helm upgrade.

Configuration

Configure the OpenNMS connection and behaviour in PLUGINS_CONFIG. Credentials are read at runtime and are never stored on a NetBox model.

PLUGINS_CONFIG = {
    "netbox_opennms": {
        # Base URL of the OpenNMS instance, including the context path.
        "opennms_url": "https://opennms.example.org/opennms",
        # A provisioning/REST role account (NOT stored on any NetBox model).
        "opennms_username": "provision-svc",
        "opennms_password": "********",          # use your secrets mechanism
        # Default OpenNMS monitoring location for requisitions that don't set one.
        # Empty means OpenNMS's built-in "Default" location.
        "default_location": "",
        # rescanExisting value passed to the import step: one of
        # "true" | "false" | "dbonly".
        "import_mode": "false",
        # Periodic drift reconciler (hourly): clears OpenNMS Foreign Sources the
        # plugin has pushed but NetBox no longer monitors — when a Requisition is
        # renamed or deleted, or its last member leaves. Ownership is tracked per
        # pushed Foreign Source, so it only ever touches requisitions the plugin
        # created, never a foreign one. "true" / "false"; needs an RQ worker.
        "reconcile_orphans": "true",
    },
}

You can verify the configured connection from the UI at Plugins → NetBox OpenNMS → Connect OpenNMS (permission-gated; it tests the configured connection and never persists credentials).

import_mode values

Value Effect on import
false (default) Import without rescanning nodes already known to OpenNMS.
true Import and rescan existing nodes (re-run detectors/policies).
dbonly Update the OpenNMS database only; do not schedule a scan.

Running the sync worker

Sync, Remove, and Move run as NetBox background Jobs — they never block the request, and a bare OpenNMS 202 ACCEPTED is reported honestly as submitted for import, never "provisioned". A NetBox RQ worker must be running for those jobs to execute:

python manage.py rqworker

If no worker is running, the Requisition and Sync pages show a warning and jobs stay pending until one starts. Each object's last-sync state (submitted / succeeded-accepted / removed / failed, with the triggering user, time, and any error) is shown on the Device/VM detail page, backed by the NetBox Job log as the audit trail.

How it works

You author a Requisition — one user-named OpenNMS Foreign Source — that owns its OpenNMS detectors and policies (discovered live from your OpenNMS instance, with a curated preset overlay for labels/defaults, or a freeform class), a set of declared services (e.g. ICMP, SNMP), and a live NetBox filter that selects its member Devices/VMs (by role, tag, site, status, custom field, …). Every member is monitored: its management IP is its primary IP unless overridden, OpenNMS auto-discovers services via the detectors, and the declared services are the guaranteed-present floor. A per-object Monitoring Override is the escape hatch (exclude an object, pin a different management IP, add extra interfaces — each with an SNMP role of Primary / Secondary / Not-eligible (snmp-primary P/S/N; at most one Primary per node) — add/suppress a service, or change its location).

Requisition filters must be disjoint: an object matched by more than one Requisition's filter is a conflict — Sync of every involved Requisition is blocked (their OpenNMS state stays untouched) until you resolve the overlap, so a node always lives in exactly one Foreign Source and nothing ever moves or disappears implicitly. A dry-run shows, per node, exactly what a Sync would add / remove / change against the live OpenNMS state before you commit.

Per-node status is graded: a Critical (red) — a filter conflict — blocks Sync; a Warning (yellow) is advisory and does not. A member with no management IP is a Warning: rather than silently skipping it, the plugin provisions an inventory-only node with no IP interface (it will not be actively monitored) and surfaces the warning in the Sync preview, the dry-run, and the Device/VM page — exclude the object if you don't want it in OpenNMS at all.

Sync renders the complete OpenNMS foreign-source definition + requisition and imports it. Membership is a live NetBox query, so adding/removing a Device or changing an attribute the filter matches simply re-resolves the Requisition; render-and-replace makes every re-sync idempotent and never duplicates a node.

OpenNMS-side setup

The plugin writes requisitions; it does not configure OpenNMS polling. For monitoring to actually happen:

  • Provisioning accountopennms_username needs a role that can read/write requisitions and trigger imports (e.g. the OpenNMS provisioning/REST role).
  • Detectors → poller packages — the requisition's detectors tell OpenNMS which services to auto-discover, but OpenNMS only polls a discovered service if a matching poller package exists for it. The plugin cannot create poller packages — ensure your poller-configuration.xml covers the services your detectors discover (and the requisition's declared services).
  • Detector/policy discovery — the detector and policy editors are populated live from your instance (GET /rest/foreignSourcesConfig/{detectors,policies}, the same API the OpenNMS UI uses), so the available classes and their parameters reflect what that OpenNMS actually has (including plugin-provided detectors). The built-in preset registry is only a curation overlay (friendly labels, sensible defaults, a shortlist). If OpenNMS is unreachable while editing, the editor degrades to the curated presets and notes it — you can still save (freeform class entry always works). Discovered results are cached briefly and refreshed at Sync.
  • Asset & metadata enrichment — a Requisition can carry NetBox inventory into OpenNMS through two channels. Asset mappings map a NetBox attribute (serial, model, site, …) to a fixed OpenNMS node asset field (the OnmsAssetRecord set, discovered from /foreignSourcesConfig/assets; validated at save). Metadata entries attach an arbitrary context/key/value triad at node / interface / service scope (context defaults to requisition; a custom context must be X--prefixed) — the open channel for anything without a fixed asset field, and the home for custom fields (cf_<name>). Values resolve per member; an unresolved value is simply omitted.
  • Minions / monitoring locations — a node assigned to a non-Default monitoring location is only polled if a Minion is registered at that location. The plugin best-effort warns when a chosen location is unknown to OpenNMS, but cannot create it. The built-in Default location is polled by the OpenNMS core (no Minion required).

Requisitions, membership, and node identity

A Requisition's name is its OpenNMS Foreign Source name — user-chosen (it must be Foreign-Source- and URL-path-safe: no whitespace or # % & + ? / \ : * ' "), not derived. Its membership is a NetBox filter (FilterSet parameters, e.g. {"role": ["switch"], "tag": ["critical"]}) applied to Devices and/or VirtualMachines per its object types; you can seed the filter by importing a NetBox Saved Filter (a one-time copy — no live link). A filter must actually constrain each selected object type, so a typo or empty value can't silently become a fleet-wide catch-all.

Filters must be disjoint. When several Requisitions match the same object it is a conflict: the object is rendered into none of them and Sync of every involved Requisition is blocked (frozen — the OpenNMS state stays exactly as last synced) until you resolve it. Resolve by narrowing a filter — typically with a negated parameter, e.g. {"role": ["switch"], "tag__n": ["critical"]} — or by excluding the object (an excluded object never conflicts; it is monitored nowhere). Conflicts are shown on the Requisition page, the Sync preview, the dry-run, and the affected Device/VM page. The REST API follows the same save-never-blocks rule but has no warning channel — after automated writes, check the Sync preview (or the requisition page) for conflicts.

Node identity is the pair (Foreign Source, type-qualified Foreign ID)device-{pk} / vm-{pk} — so a re-sync updates a node in place and renaming a Device only relabels it (never a duplicate). Moving an object between Requisitions (a filter change) changes its Foreign Source, which OpenNMS treats as a new node; the per-node dry-run surfaces such moves — and every add / remove / change against the live OpenNMS state — before you Sync.

Development & contributing

Full project-structure, build, dev-environment, testing, and release docs live in the contributor guide. The short version:

pip install -e .     # editable install
make verify          # ruff lint + full unit suite (what CI gates on)
make integration     # live round-trip against a disposable OpenNMS Horizon 36

Before a PR: make verify green, Conventional Commits, signed commits (git commit -s + commit.gpgsign), and an SPDX header on every source file. CI runs the same Makefile targets, so local and CI stay identical.

License

MIT — see LICENSE. Every source file carries an SPDX header.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

netbox_opennms_plugin-0.0.4.tar.gz (80.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

netbox_opennms_plugin-0.0.4-py3-none-any.whl (96.9 kB view details)

Uploaded Python 3

File details

Details for the file netbox_opennms_plugin-0.0.4.tar.gz.

File metadata

  • Download URL: netbox_opennms_plugin-0.0.4.tar.gz
  • Upload date:
  • Size: 80.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for netbox_opennms_plugin-0.0.4.tar.gz
Algorithm Hash digest
SHA256 2e149ef139c77e0e981bbd165b556eb9c589a344ed7cc19a5d67051beb687405
MD5 f732b0a0cb06a148a907ae973445d527
BLAKE2b-256 1c469daa8be3afb1457a94d1d01dd31fddba03f8c46ded9faf06873d8f45befd

See more details on using hashes here.

Provenance

The following attestation bundles were made for netbox_opennms_plugin-0.0.4.tar.gz:

Publisher: release.yml on no42-org/netbox-opennms-plugin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file netbox_opennms_plugin-0.0.4-py3-none-any.whl.

File metadata

File hashes

Hashes for netbox_opennms_plugin-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 9efa640c4d27a791ac3399697ae8b9a0bf736194df6802f47f4793efc39bd723
MD5 457c1934577b21bd0bb2b5381afc7c05
BLAKE2b-256 311bfc63806f8f08c29c6b42df76f0f9ec0352797e304fb4080c60dfbf9b2774

See more details on using hashes here.

Provenance

The following attestation bundles were made for netbox_opennms_plugin-0.0.4-py3-none-any.whl:

Publisher: release.yml on no42-org/netbox-opennms-plugin

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page