netbox-plugin-prometheus-sd 2.0.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netbox_plugin_prometheus_sd-2.2.0/PKG-INFO +297 -0
- netbox_plugin_prometheus_sd-2.2.0/README.md +278 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/__init__.py +1 -1
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/filtersets.py +33 -5
- netbox_plugin_prometheus_sd-2.2.0/netbox_prometheus_sd/tests/test_filtersets.py +63 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/test_serializers.py +13 -2
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/utils.py +22 -4
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/pyproject.toml +1 -1
- netbox_plugin_prometheus_sd-2.0.0/PKG-INFO +0 -158
- netbox_plugin_prometheus_sd-2.0.0/README.md +0 -141
- netbox_plugin_prometheus_sd-2.0.0/netbox_prometheus_sd/tests/test_filtersets.py +0 -36
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/LICENSE +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/__init__.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/serializers.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/urls.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/utils.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/views.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/__init__.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/test_api.py +0 -0
- {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/test_utils.py +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netbox-plugin-prometheus-sd
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: A Netbox plugin to provide Netbox entires to Prometheus HTTP service discovery
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Felix Peters
|
|
8
|
+
Author-email: felix.peters@breuninger.de
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# netbox-plugin-prometheus-sd
|
|
20
|
+
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
[](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
|
|
23
|
+
[](https://pypi.org/project/netbox-plugin-prometheus-sd/)
|
|
24
|
+
|
|
25
|
+
[](https://www.buymeacoffee.com/flxpeters)
|
|
26
|
+
|
|
27
|
+
Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
|
|
28
|
+
|
|
29
|
+
HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
|
|
30
|
+
This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
|
|
31
|
+
|
|
32
|
+
## Compatibility
|
|
33
|
+
|
|
34
|
+
We aim to support the latest major versions of Netbox.
|
|
35
|
+
|
|
36
|
+
| Plugin | Netbox |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `2.x` | `>= 4.0` |
|
|
39
|
+
| `1.x` | `3.x` (no longer maintained) |
|
|
40
|
+
|
|
41
|
+
Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
|
|
42
|
+
Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
|
|
43
|
+
|
|
44
|
+
Plugin `2.0` also fixed a set of N+1 queries that made the endpoints very slow on
|
|
45
|
+
larger installations ([#265](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/issues/265)).
|
|
46
|
+
If service discovery is putting noticeable load on your Netbox database, upgrading
|
|
47
|
+
is worthwhile.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
The plugin is available as a Python package in pypi and can be installed with pip
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install netbox-plugin-prometheus-sd
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
PLUGINS = ['netbox_prometheus_sd']
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The plugin has not further plugin configuration.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
|
|
68
|
+
|
|
69
|
+
### API
|
|
70
|
+
|
|
71
|
+
The plugin reuses Netbox API view sets with new serializers for Prometheus.
|
|
72
|
+
This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
|
|
73
|
+
Paging is disabled because Prometheus does not support paged results.
|
|
74
|
+
|
|
75
|
+
The plugin also reuses the Netbox authentication and permission model.
|
|
76
|
+
Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
|
|
80
|
+
GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
|
|
81
|
+
GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
|
|
82
|
+
GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Extended services filters
|
|
86
|
+
|
|
87
|
+
Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>`, and
|
|
88
|
+
`site=<slug>` or `site_id=<id>` parameters. The lookup is only executed against the `tenant`/`site` attribute of the
|
|
89
|
+
object associated with the service.
|
|
90
|
+
|
|
91
|
+
#### Filtering and response size
|
|
92
|
+
|
|
93
|
+
Because paging is disabled, one request serializes every object the token is
|
|
94
|
+
allowed to see. Filtering with the standard Netbox query parameters is the
|
|
95
|
+
supported way to keep responses small, and it is usually what you want anyway —
|
|
96
|
+
Prometheus should not be told about decommissioned hardware:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
/api/plugins/prometheus-sd/devices/?status=active&tag=monitoring&site=dc1
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Labels
|
|
103
|
+
|
|
104
|
+
Every target carries `__meta_netbox_*` labels describing the Netbox object.
|
|
105
|
+
**Prometheus discards labels starting with `__` after service discovery**, so
|
|
106
|
+
they have to be copied into real labels with `relabel_configs` (see below) if you
|
|
107
|
+
want them on your metrics.
|
|
108
|
+
|
|
109
|
+
A label is only present when the underlying field is populated — a device with no
|
|
110
|
+
tenant has no `__meta_netbox_tenant`.
|
|
111
|
+
|
|
112
|
+
| Label | Devices | VMs | Services | IPs |
|
|
113
|
+
|---|:-:|:-:|:-:|:-:|
|
|
114
|
+
| `id`, `name`, `status`, `model` | ✓ | ✓ | id/name only | ✓ (no name) |
|
|
115
|
+
| `primary_ip`, `primary_ip4`, `primary_ip6` | ✓ | ✓ | from parent | |
|
|
116
|
+
| `oob_ip` | ✓ | | from parent | |
|
|
117
|
+
| `ip` | | | | ✓ |
|
|
118
|
+
| `role`, `role_slug` | ✓ | ✓ | | `role` only |
|
|
119
|
+
| `platform`, `platform_slug` | ✓ | ✓ | | |
|
|
120
|
+
| `device_type`, `device_type_slug` | ✓ | | | |
|
|
121
|
+
| `site`, `site_slug` | ✓ | ✓ | from parent | |
|
|
122
|
+
| `scope`, `scope_slug` | | ✓ ¹ | | |
|
|
123
|
+
| `location`, `location_slug` | ✓ | | | |
|
|
124
|
+
| `rack`, `rack_u_position` | ✓ | | | |
|
|
125
|
+
| `cluster`, `cluster_group`, `cluster_type` | ✓ ² | ✓ | | |
|
|
126
|
+
| `tenant`, `tenant_slug` | ✓ | ✓ | from parent | ✓ |
|
|
127
|
+
| `tenant_group`, `tenant_group_slug` | ✓ | ✓ | from parent | ✓ |
|
|
128
|
+
| `tags`, `tag_slugs` | ✓ | ✓ | ✓ | ✓ |
|
|
129
|
+
| `services` | ✓ | ✓ | | |
|
|
130
|
+
| `contact_<priority>_{name,email,comments,role}` | ✓ | ✓ | from parent | |
|
|
131
|
+
| `custom_field_<name>` | ✓ | ✓ | ✓ | ✓ |
|
|
132
|
+
| `description` | ✓ | | | |
|
|
133
|
+
| `parent`, `display`, `ports`, `ipaddresses` | | | ✓ | |
|
|
134
|
+
|
|
135
|
+
¹ Netbox 4.2 replaced the cluster's site with a generic `scope`. On 4.2+ a VM
|
|
136
|
+
emits `scope`/`scope_slug` for the cluster, and `site`/`site_slug` for its own
|
|
137
|
+
site; below 4.2 the cluster's site is reported as `site`.
|
|
138
|
+
² Only when the device is assigned to a cluster.
|
|
139
|
+
|
|
140
|
+
Config context can additionally set `__metrics_path__` and `__scheme__`, which
|
|
141
|
+
Prometheus consumes directly (see [Config context](#config-context)).
|
|
142
|
+
|
|
143
|
+
### Relabeling
|
|
144
|
+
|
|
145
|
+
The `__meta_netbox_*` labels are dropped unless you map them. A typical device job
|
|
146
|
+
scraping node_exporter on the primary IP:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
scrape_configs:
|
|
150
|
+
- job_name: netbox-devices
|
|
151
|
+
http_sd_configs:
|
|
152
|
+
- url: http://netbox:8080/api/plugins/prometheus-sd/devices/?status=active&tag=monitoring
|
|
153
|
+
refresh_interval: 60s
|
|
154
|
+
authorization:
|
|
155
|
+
type: Token
|
|
156
|
+
credentials: "<your-netbox-api-token>"
|
|
157
|
+
|
|
158
|
+
relabel_configs:
|
|
159
|
+
# Skip anything without a primary IPv4, otherwise the address below is empty.
|
|
160
|
+
- source_labels: [__meta_netbox_primary_ip4]
|
|
161
|
+
regex: ^$
|
|
162
|
+
action: drop
|
|
163
|
+
|
|
164
|
+
# Scrape the primary IP instead of the device name, which may not resolve.
|
|
165
|
+
- source_labels: [__meta_netbox_primary_ip4]
|
|
166
|
+
target_label: __address__
|
|
167
|
+
replacement: "$1:9100"
|
|
168
|
+
|
|
169
|
+
# Keep the Netbox name as the instance label rather than the IP.
|
|
170
|
+
- source_labels: [__meta_netbox_name]
|
|
171
|
+
target_label: instance
|
|
172
|
+
|
|
173
|
+
# Promote the dimensions worth alerting and grouping on.
|
|
174
|
+
- source_labels: [__meta_netbox_site_slug]
|
|
175
|
+
target_label: site
|
|
176
|
+
- source_labels: [__meta_netbox_role_slug]
|
|
177
|
+
target_label: role
|
|
178
|
+
- source_labels: [__meta_netbox_tenant_slug]
|
|
179
|
+
target_label: tenant
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Two things worth knowing:
|
|
183
|
+
|
|
184
|
+
- `tags` and `tag_slugs` are comma-joined, so match them with `.*,?value,?.*`
|
|
185
|
+
rather than `=`.
|
|
186
|
+
- Devices and VMs use the same label names, so one set of `relabel_configs` can
|
|
187
|
+
be reused across both jobs.
|
|
188
|
+
|
|
189
|
+
### Config context
|
|
190
|
+
|
|
191
|
+
The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
|
|
192
|
+
If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
prometheus-plugin-prometheus-sd:
|
|
196
|
+
- metrics_path: /not/metrics
|
|
197
|
+
port: 4242
|
|
198
|
+
scheme: https
|
|
199
|
+
- port: 4243
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
|
|
203
|
+
config and filtering each scenario by a specific tag for instance.
|
|
204
|
+
|
|
205
|
+
If there is only one entry you can also use this form:
|
|
206
|
+
|
|
207
|
+
```yaml
|
|
208
|
+
prometheus-plugin-prometheus-sd:
|
|
209
|
+
metrics_path: /not/metrics
|
|
210
|
+
port: 4242
|
|
211
|
+
scheme: https
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Example
|
|
215
|
+
|
|
216
|
+
[`example/prometheus.yml`](example/prometheus.yml) is a complete Prometheus
|
|
217
|
+
configuration covering all four endpoints, with `relabel_configs` mapping the
|
|
218
|
+
`__meta_netbox_*` labels onto real ones.
|
|
219
|
+
|
|
220
|
+
It is not a snippet that happens to be in the repository: the test suite runs
|
|
221
|
+
Prometheus against this exact file and asserts that the expected targets and
|
|
222
|
+
labels are discovered, so it cannot quietly stop working.
|
|
223
|
+
|
|
224
|
+
To see it running, with a Netbox that has demo data already loaded:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
poetry run invoke build-dev
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
- Netbox: <http://localhost:8000> (`admin` / `admin`)
|
|
231
|
+
- Prometheus: <http://localhost:9090> — discovered targets are under
|
|
232
|
+
Status → Target health, and expanding one shows the raw `__meta_netbox_*`
|
|
233
|
+
labels before relabeling.
|
|
234
|
+
|
|
235
|
+
The demo data is the unit-test fixtures, so it also exercises config context
|
|
236
|
+
(the VMs get two targets each, on different ports), services, contacts and tags.
|
|
237
|
+
|
|
238
|
+
## Development
|
|
239
|
+
|
|
240
|
+
We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
|
|
241
|
+
To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
|
|
242
|
+
which creates "throwaway, lightweight" Netbox Docker containers.
|
|
243
|
+
|
|
244
|
+
Install the dependencies with `poetry install`, then run the tasks through
|
|
245
|
+
`poetry run` (this works on every Poetry version, whereas `poetry shell` was moved
|
|
246
|
+
into a separate plugin in Poetry 2.0):
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Unit tests plus the Prometheus end-to-end check
|
|
250
|
+
poetry run invoke test
|
|
251
|
+
|
|
252
|
+
# Test against a specific Netbox release (default: latest)
|
|
253
|
+
NETBOX_VER=v4.6.5 poetry run invoke test
|
|
254
|
+
|
|
255
|
+
# Either half on its own
|
|
256
|
+
poetry run invoke unittest
|
|
257
|
+
poetry run invoke test-prometheus
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The Netbox image is built from the `Dockerfile` in the repository root.
|
|
261
|
+
|
|
262
|
+
Testing has two layers:
|
|
263
|
+
|
|
264
|
+
- **Unit tests** — plain Django tests under `netbox_prometheus_sd/tests/`,
|
|
265
|
+
executed inside the Netbox container. These cover the serializers and the
|
|
266
|
+
label output.
|
|
267
|
+
- **Prometheus end-to-end** — Netbox serving real HTTP with seeded data, and a
|
|
268
|
+
real Prometheus configured from `example/prometheus.yml`. It asserts that the
|
|
269
|
+
expected jobs discover targets and that relabeling produced the expected
|
|
270
|
+
labels.
|
|
271
|
+
|
|
272
|
+
The second layer exists because "valid JSON with targets and labels" is not the
|
|
273
|
+
same as "Prometheus accepts this as an `http_sd` source". A response that
|
|
274
|
+
Prometheus rejects would pass every unit test in this repository. It also keeps
|
|
275
|
+
the documented example honest, since it is the file under test.
|
|
276
|
+
|
|
277
|
+
Features should be covered by a test, but sometimes it is easier to develop
|
|
278
|
+
against a running system:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Netbox + Prometheus with demo data, left running until Ctrl+C
|
|
282
|
+
poetry run invoke build-dev
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Netbox is on <http://localhost:8000> (`admin` / `admin`) and Prometheus on
|
|
286
|
+
<http://localhost:9090>, already scraping it.
|
|
287
|
+
|
|
288
|
+
API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
|
|
289
|
+
|
|
290
|
+
## Conventional Commits
|
|
291
|
+
|
|
292
|
+
This repository follows the Conventional Commits specification for versioning and changelog generation.
|
|
293
|
+
Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
|
|
294
|
+
about the changes made. Each commit message follows a defined format that includes a type,
|
|
295
|
+
an optional scope, and a message. The types typically include features, fixes, documentation, and more.
|
|
296
|
+
By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
|
|
297
|
+
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# netbox-plugin-prometheus-sd
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
|
|
5
|
+
[](https://pypi.org/project/netbox-plugin-prometheus-sd/)
|
|
6
|
+
|
|
7
|
+
[](https://www.buymeacoffee.com/flxpeters)
|
|
8
|
+
|
|
9
|
+
Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
|
|
10
|
+
|
|
11
|
+
HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
|
|
12
|
+
This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
|
|
13
|
+
|
|
14
|
+
## Compatibility
|
|
15
|
+
|
|
16
|
+
We aim to support the latest major versions of Netbox.
|
|
17
|
+
|
|
18
|
+
| Plugin | Netbox |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `2.x` | `>= 4.0` |
|
|
21
|
+
| `1.x` | `3.x` (no longer maintained) |
|
|
22
|
+
|
|
23
|
+
Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
|
|
24
|
+
Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
|
|
25
|
+
|
|
26
|
+
Plugin `2.0` also fixed a set of N+1 queries that made the endpoints very slow on
|
|
27
|
+
larger installations ([#265](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/issues/265)).
|
|
28
|
+
If service discovery is putting noticeable load on your Netbox database, upgrading
|
|
29
|
+
is worthwhile.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
The plugin is available as a Python package in pypi and can be installed with pip
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install netbox-plugin-prometheus-sd
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
PLUGINS = ['netbox_prometheus_sd']
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The plugin has not further plugin configuration.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
|
|
50
|
+
|
|
51
|
+
### API
|
|
52
|
+
|
|
53
|
+
The plugin reuses Netbox API view sets with new serializers for Prometheus.
|
|
54
|
+
This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
|
|
55
|
+
Paging is disabled because Prometheus does not support paged results.
|
|
56
|
+
|
|
57
|
+
The plugin also reuses the Netbox authentication and permission model.
|
|
58
|
+
Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
|
|
62
|
+
GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
|
|
63
|
+
GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
|
|
64
|
+
GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Extended services filters
|
|
68
|
+
|
|
69
|
+
Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>`, and
|
|
70
|
+
`site=<slug>` or `site_id=<id>` parameters. The lookup is only executed against the `tenant`/`site` attribute of the
|
|
71
|
+
object associated with the service.
|
|
72
|
+
|
|
73
|
+
#### Filtering and response size
|
|
74
|
+
|
|
75
|
+
Because paging is disabled, one request serializes every object the token is
|
|
76
|
+
allowed to see. Filtering with the standard Netbox query parameters is the
|
|
77
|
+
supported way to keep responses small, and it is usually what you want anyway —
|
|
78
|
+
Prometheus should not be told about decommissioned hardware:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
/api/plugins/prometheus-sd/devices/?status=active&tag=monitoring&site=dc1
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Labels
|
|
85
|
+
|
|
86
|
+
Every target carries `__meta_netbox_*` labels describing the Netbox object.
|
|
87
|
+
**Prometheus discards labels starting with `__` after service discovery**, so
|
|
88
|
+
they have to be copied into real labels with `relabel_configs` (see below) if you
|
|
89
|
+
want them on your metrics.
|
|
90
|
+
|
|
91
|
+
A label is only present when the underlying field is populated — a device with no
|
|
92
|
+
tenant has no `__meta_netbox_tenant`.
|
|
93
|
+
|
|
94
|
+
| Label | Devices | VMs | Services | IPs |
|
|
95
|
+
|---|:-:|:-:|:-:|:-:|
|
|
96
|
+
| `id`, `name`, `status`, `model` | ✓ | ✓ | id/name only | ✓ (no name) |
|
|
97
|
+
| `primary_ip`, `primary_ip4`, `primary_ip6` | ✓ | ✓ | from parent | |
|
|
98
|
+
| `oob_ip` | ✓ | | from parent | |
|
|
99
|
+
| `ip` | | | | ✓ |
|
|
100
|
+
| `role`, `role_slug` | ✓ | ✓ | | `role` only |
|
|
101
|
+
| `platform`, `platform_slug` | ✓ | ✓ | | |
|
|
102
|
+
| `device_type`, `device_type_slug` | ✓ | | | |
|
|
103
|
+
| `site`, `site_slug` | ✓ | ✓ | from parent | |
|
|
104
|
+
| `scope`, `scope_slug` | | ✓ ¹ | | |
|
|
105
|
+
| `location`, `location_slug` | ✓ | | | |
|
|
106
|
+
| `rack`, `rack_u_position` | ✓ | | | |
|
|
107
|
+
| `cluster`, `cluster_group`, `cluster_type` | ✓ ² | ✓ | | |
|
|
108
|
+
| `tenant`, `tenant_slug` | ✓ | ✓ | from parent | ✓ |
|
|
109
|
+
| `tenant_group`, `tenant_group_slug` | ✓ | ✓ | from parent | ✓ |
|
|
110
|
+
| `tags`, `tag_slugs` | ✓ | ✓ | ✓ | ✓ |
|
|
111
|
+
| `services` | ✓ | ✓ | | |
|
|
112
|
+
| `contact_<priority>_{name,email,comments,role}` | ✓ | ✓ | from parent | |
|
|
113
|
+
| `custom_field_<name>` | ✓ | ✓ | ✓ | ✓ |
|
|
114
|
+
| `description` | ✓ | | | |
|
|
115
|
+
| `parent`, `display`, `ports`, `ipaddresses` | | | ✓ | |
|
|
116
|
+
|
|
117
|
+
¹ Netbox 4.2 replaced the cluster's site with a generic `scope`. On 4.2+ a VM
|
|
118
|
+
emits `scope`/`scope_slug` for the cluster, and `site`/`site_slug` for its own
|
|
119
|
+
site; below 4.2 the cluster's site is reported as `site`.
|
|
120
|
+
² Only when the device is assigned to a cluster.
|
|
121
|
+
|
|
122
|
+
Config context can additionally set `__metrics_path__` and `__scheme__`, which
|
|
123
|
+
Prometheus consumes directly (see [Config context](#config-context)).
|
|
124
|
+
|
|
125
|
+
### Relabeling
|
|
126
|
+
|
|
127
|
+
The `__meta_netbox_*` labels are dropped unless you map them. A typical device job
|
|
128
|
+
scraping node_exporter on the primary IP:
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
scrape_configs:
|
|
132
|
+
- job_name: netbox-devices
|
|
133
|
+
http_sd_configs:
|
|
134
|
+
- url: http://netbox:8080/api/plugins/prometheus-sd/devices/?status=active&tag=monitoring
|
|
135
|
+
refresh_interval: 60s
|
|
136
|
+
authorization:
|
|
137
|
+
type: Token
|
|
138
|
+
credentials: "<your-netbox-api-token>"
|
|
139
|
+
|
|
140
|
+
relabel_configs:
|
|
141
|
+
# Skip anything without a primary IPv4, otherwise the address below is empty.
|
|
142
|
+
- source_labels: [__meta_netbox_primary_ip4]
|
|
143
|
+
regex: ^$
|
|
144
|
+
action: drop
|
|
145
|
+
|
|
146
|
+
# Scrape the primary IP instead of the device name, which may not resolve.
|
|
147
|
+
- source_labels: [__meta_netbox_primary_ip4]
|
|
148
|
+
target_label: __address__
|
|
149
|
+
replacement: "$1:9100"
|
|
150
|
+
|
|
151
|
+
# Keep the Netbox name as the instance label rather than the IP.
|
|
152
|
+
- source_labels: [__meta_netbox_name]
|
|
153
|
+
target_label: instance
|
|
154
|
+
|
|
155
|
+
# Promote the dimensions worth alerting and grouping on.
|
|
156
|
+
- source_labels: [__meta_netbox_site_slug]
|
|
157
|
+
target_label: site
|
|
158
|
+
- source_labels: [__meta_netbox_role_slug]
|
|
159
|
+
target_label: role
|
|
160
|
+
- source_labels: [__meta_netbox_tenant_slug]
|
|
161
|
+
target_label: tenant
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Two things worth knowing:
|
|
165
|
+
|
|
166
|
+
- `tags` and `tag_slugs` are comma-joined, so match them with `.*,?value,?.*`
|
|
167
|
+
rather than `=`.
|
|
168
|
+
- Devices and VMs use the same label names, so one set of `relabel_configs` can
|
|
169
|
+
be reused across both jobs.
|
|
170
|
+
|
|
171
|
+
### Config context
|
|
172
|
+
|
|
173
|
+
The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
|
|
174
|
+
If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
|
|
175
|
+
|
|
176
|
+
```yaml
|
|
177
|
+
prometheus-plugin-prometheus-sd:
|
|
178
|
+
- metrics_path: /not/metrics
|
|
179
|
+
port: 4242
|
|
180
|
+
scheme: https
|
|
181
|
+
- port: 4243
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
|
|
185
|
+
config and filtering each scenario by a specific tag for instance.
|
|
186
|
+
|
|
187
|
+
If there is only one entry you can also use this form:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
prometheus-plugin-prometheus-sd:
|
|
191
|
+
metrics_path: /not/metrics
|
|
192
|
+
port: 4242
|
|
193
|
+
scheme: https
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Example
|
|
197
|
+
|
|
198
|
+
[`example/prometheus.yml`](example/prometheus.yml) is a complete Prometheus
|
|
199
|
+
configuration covering all four endpoints, with `relabel_configs` mapping the
|
|
200
|
+
`__meta_netbox_*` labels onto real ones.
|
|
201
|
+
|
|
202
|
+
It is not a snippet that happens to be in the repository: the test suite runs
|
|
203
|
+
Prometheus against this exact file and asserts that the expected targets and
|
|
204
|
+
labels are discovered, so it cannot quietly stop working.
|
|
205
|
+
|
|
206
|
+
To see it running, with a Netbox that has demo data already loaded:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
poetry run invoke build-dev
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- Netbox: <http://localhost:8000> (`admin` / `admin`)
|
|
213
|
+
- Prometheus: <http://localhost:9090> — discovered targets are under
|
|
214
|
+
Status → Target health, and expanding one shows the raw `__meta_netbox_*`
|
|
215
|
+
labels before relabeling.
|
|
216
|
+
|
|
217
|
+
The demo data is the unit-test fixtures, so it also exercises config context
|
|
218
|
+
(the VMs get two targets each, on different ports), services, contacts and tags.
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
|
|
223
|
+
To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
|
|
224
|
+
which creates "throwaway, lightweight" Netbox Docker containers.
|
|
225
|
+
|
|
226
|
+
Install the dependencies with `poetry install`, then run the tasks through
|
|
227
|
+
`poetry run` (this works on every Poetry version, whereas `poetry shell` was moved
|
|
228
|
+
into a separate plugin in Poetry 2.0):
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
# Unit tests plus the Prometheus end-to-end check
|
|
232
|
+
poetry run invoke test
|
|
233
|
+
|
|
234
|
+
# Test against a specific Netbox release (default: latest)
|
|
235
|
+
NETBOX_VER=v4.6.5 poetry run invoke test
|
|
236
|
+
|
|
237
|
+
# Either half on its own
|
|
238
|
+
poetry run invoke unittest
|
|
239
|
+
poetry run invoke test-prometheus
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The Netbox image is built from the `Dockerfile` in the repository root.
|
|
243
|
+
|
|
244
|
+
Testing has two layers:
|
|
245
|
+
|
|
246
|
+
- **Unit tests** — plain Django tests under `netbox_prometheus_sd/tests/`,
|
|
247
|
+
executed inside the Netbox container. These cover the serializers and the
|
|
248
|
+
label output.
|
|
249
|
+
- **Prometheus end-to-end** — Netbox serving real HTTP with seeded data, and a
|
|
250
|
+
real Prometheus configured from `example/prometheus.yml`. It asserts that the
|
|
251
|
+
expected jobs discover targets and that relabeling produced the expected
|
|
252
|
+
labels.
|
|
253
|
+
|
|
254
|
+
The second layer exists because "valid JSON with targets and labels" is not the
|
|
255
|
+
same as "Prometheus accepts this as an `http_sd` source". A response that
|
|
256
|
+
Prometheus rejects would pass every unit test in this repository. It also keeps
|
|
257
|
+
the documented example honest, since it is the file under test.
|
|
258
|
+
|
|
259
|
+
Features should be covered by a test, but sometimes it is easier to develop
|
|
260
|
+
against a running system:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Netbox + Prometheus with demo data, left running until Ctrl+C
|
|
264
|
+
poetry run invoke build-dev
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Netbox is on <http://localhost:8000> (`admin` / `admin`) and Prometheus on
|
|
268
|
+
<http://localhost:9090>, already scraping it.
|
|
269
|
+
|
|
270
|
+
API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
|
|
271
|
+
|
|
272
|
+
## Conventional Commits
|
|
273
|
+
|
|
274
|
+
This repository follows the Conventional Commits specification for versioning and changelog generation.
|
|
275
|
+
Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
|
|
276
|
+
about the changes made. Each commit message follows a defined format that includes a type,
|
|
277
|
+
an optional scope, and a message. The types typically include features, fixes, documentation, and more.
|
|
278
|
+
By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
|
|
@@ -8,14 +8,19 @@ from utilities.filters import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
from ipam.filtersets import ServiceFilterSet as NetboxServiceFilterSet
|
|
11
|
+
from ipam.models import Service
|
|
12
|
+
|
|
13
|
+
# Netbox 4.7 replaced the Service.ports ArrayField with `port_mappings`, and
|
|
14
|
+
# its filterset metaclass rejects a filter on a field that no longer exists.
|
|
15
|
+
SERVICE_HAS_PORTS_FIELD = any(f.name == "ports" for f in Service._meta.get_fields())
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
class ServiceFilterSet(NetboxServiceFilterSet):
|
|
14
|
-
"""Filter set to support tenancy over the device/VM foreign key.
|
|
19
|
+
"""Filter set to support tenancy and site filtering over the device/VM foreign key.
|
|
15
20
|
|
|
16
|
-
Tenancy in Netbox is very
|
|
21
|
+
Tenancy in Netbox is very inconsistent and the relationship on its own is defined across many different models. This
|
|
17
22
|
means that supporting all layers is nearly impossible without a stronger upstream support. For this reason only the
|
|
18
|
-
"first level" tenancy is supported by
|
|
23
|
+
"first level" tenancy is supported by the tenant filter set.
|
|
19
24
|
"""
|
|
20
25
|
|
|
21
26
|
tenant_id = MultiValueNumberFilter(
|
|
@@ -28,9 +33,21 @@ class ServiceFilterSet(NetboxServiceFilterSet):
|
|
|
28
33
|
label=_("Tenant (slug)"),
|
|
29
34
|
)
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
site_id = MultiValueNumberFilter(
|
|
37
|
+
method="filter_by_site_id",
|
|
38
|
+
label=_("Site (ID)"),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
site = MultiValueCharFilter(
|
|
42
|
+
method="filter_by_site_slug",
|
|
43
|
+
label=_("Site (slug)"),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Netbox < 4.7 only exposes the ports array as `port`, but Netbox's
|
|
47
|
+
# test_missing_filters wants a filter named after the model field.
|
|
32
48
|
# see: https://github.com/netbox-community/netbox/blob/master/netbox/utilities/testing/filtersets.py#L98
|
|
33
|
-
|
|
49
|
+
if SERVICE_HAS_PORTS_FIELD:
|
|
50
|
+
ports = NumericArrayFilter(field_name="ports", lookup_expr="contains")
|
|
34
51
|
|
|
35
52
|
def filter_by_cluster_tenant_id(self, queryset, name, value):
|
|
36
53
|
return queryset.filter(
|
|
@@ -54,3 +71,14 @@ class ServiceFilterSet(NetboxServiceFilterSet):
|
|
|
54
71
|
Q(device__tenant__slug__in=value)
|
|
55
72
|
| Q(virtual_machine__tenant__slug__in=value)
|
|
56
73
|
)
|
|
74
|
+
|
|
75
|
+
def filter_by_site_id(self, queryset, name, value):
|
|
76
|
+
return queryset.filter(
|
|
77
|
+
Q(device__site_id__in=value) | Q(virtual_machine__site_id__in=value)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def filter_by_site_slug(self, queryset, name, value):
|
|
81
|
+
return queryset.filter(
|
|
82
|
+
Q(device__site__slug__in=value)
|
|
83
|
+
| Q(virtual_machine__site__slug__in=value)
|
|
84
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
from dcim.models.sites import Site
|
|
4
|
+
from ipam.models import Service
|
|
5
|
+
from tenancy.models import Tenant
|
|
6
|
+
|
|
7
|
+
try: # NetBox 4.7+
|
|
8
|
+
from utilities.testing import ChangeLoggedFilterSetTestMixin
|
|
9
|
+
except ImportError: # NetBox <4.7
|
|
10
|
+
from utilities.testing import (
|
|
11
|
+
ChangeLoggedFilterSetTests as ChangeLoggedFilterSetTestMixin,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from . import utils
|
|
15
|
+
from ..filtersets import ServiceFilterSet
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ServiceTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
|
|
19
|
+
queryset = Service.objects.all()
|
|
20
|
+
filterset = ServiceFilterSet
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def setUpTestData(cls):
|
|
24
|
+
"""Netbox requires us to define test data in this method, otherwise the ORM won't pick them."""
|
|
25
|
+
for i in range(1, 4):
|
|
26
|
+
utils.build_device_full(f"firewall-full-0{i}", i)
|
|
27
|
+
utils.build_vm_full(f"vm-full-0{i}.example.com", i)
|
|
28
|
+
|
|
29
|
+
def test_device_tenant(self):
|
|
30
|
+
tenant = Tenant.objects.all()[0]
|
|
31
|
+
|
|
32
|
+
params = {"tenant_id": [tenant.pk]}
|
|
33
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
34
|
+
params = {"tenant": [tenant.slug]}
|
|
35
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
36
|
+
|
|
37
|
+
def test_vm_tenant(self):
|
|
38
|
+
tenant = Tenant.objects.all()[0]
|
|
39
|
+
|
|
40
|
+
params = {"tenant_id": [tenant.pk]}
|
|
41
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
42
|
+
params = {"tenant": [tenant.slug]}
|
|
43
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
44
|
+
|
|
45
|
+
def test_device_site(self):
|
|
46
|
+
# build_device_full() assigns devices directly to the "site" site.
|
|
47
|
+
site = Site.objects.get(slug="site")
|
|
48
|
+
|
|
49
|
+
params = {"site_id": [site.pk]}
|
|
50
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
51
|
+
params = {"site": [site.slug]}
|
|
52
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
53
|
+
|
|
54
|
+
def test_vm_site(self):
|
|
55
|
+
# build_vm_full() assigns VMs directly to the "campus-a" site, distinct
|
|
56
|
+
# from the devices' site, so this also verifies the filter doesn't match
|
|
57
|
+
# across device/VM boundaries.
|
|
58
|
+
site = Site.objects.get(slug="campus-a")
|
|
59
|
+
|
|
60
|
+
params = {"site_id": [site.pk]}
|
|
61
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
62
|
+
params = {"site": [site.slug]}
|
|
63
|
+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
@@ -9,6 +9,7 @@ from . import utils
|
|
|
9
9
|
|
|
10
10
|
from ..api.utils import NETBOX_RELEASE_CURRENT, NETBOX_RELEASE_41
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class PrometheusVirtualMachineSerializerTests(TestCase):
|
|
13
14
|
def test_vm_minimal_to_target(self):
|
|
14
15
|
|
|
@@ -498,7 +499,12 @@ class PrometheusServiceSerializerTests(TestCase):
|
|
|
498
499
|
)
|
|
499
500
|
self.assertTrue(
|
|
500
501
|
utils.dictContainsSubset(
|
|
501
|
-
{
|
|
502
|
+
{
|
|
503
|
+
"__meta_netbox_display": utils.expected_service_display(
|
|
504
|
+
"ssh", "TCP", 22
|
|
505
|
+
)
|
|
506
|
+
},
|
|
507
|
+
data["labels"],
|
|
502
508
|
)
|
|
503
509
|
)
|
|
504
510
|
self.assertTrue(
|
|
@@ -562,7 +568,12 @@ class PrometheusServiceSerializerTests(TestCase):
|
|
|
562
568
|
)
|
|
563
569
|
self.assertTrue(
|
|
564
570
|
utils.dictContainsSubset(
|
|
565
|
-
{
|
|
571
|
+
{
|
|
572
|
+
"__meta_netbox_display": utils.expected_service_display(
|
|
573
|
+
"ssh", "TCP", 22
|
|
574
|
+
)
|
|
575
|
+
},
|
|
576
|
+
data["labels"],
|
|
566
577
|
)
|
|
567
578
|
)
|
|
568
579
|
self.assertTrue(
|
|
@@ -29,7 +29,7 @@ def dictContainsSubset(subset, fullset):
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def build_cluster():
|
|
32
|
-
try:
|
|
32
|
+
try: # NetBox 4.2+
|
|
33
33
|
scope_type = ContentType.objects.get_for_model(Site)
|
|
34
34
|
return Cluster.objects.get_or_create(
|
|
35
35
|
name="DC1",
|
|
@@ -38,7 +38,7 @@ def build_cluster():
|
|
|
38
38
|
scope_type=scope_type,
|
|
39
39
|
scope_id=Site.objects.get_or_create(name="Campus A", slug="campus-a")[0].id,
|
|
40
40
|
)[0]
|
|
41
|
-
except FieldError:
|
|
41
|
+
except FieldError: # NetBox <4.2
|
|
42
42
|
return Cluster.objects.get_or_create(
|
|
43
43
|
name="DC1",
|
|
44
44
|
group=ClusterGroup.objects.get_or_create(name="VMware")[0],
|
|
@@ -119,6 +119,10 @@ def build_vm_full(name, ip_octet=1):
|
|
|
119
119
|
)[0]
|
|
120
120
|
|
|
121
121
|
vm.tenant = build_tenant()
|
|
122
|
+
# Direct site assignment, independent of the cluster's own site/scope -- Netbox
|
|
123
|
+
# supports both, and the plugin's ServiceFilterSet only filters on this direct
|
|
124
|
+
# relation (see filtersets.py).
|
|
125
|
+
vm.site = Site.objects.get_or_create(name="Campus A", slug="campus-a")[0]
|
|
122
126
|
vm.custom_field_data = build_custom_fields()
|
|
123
127
|
vm.role = DeviceRole.objects.get_or_create(name="VM", slug="vm", vm_role=True)[0]
|
|
124
128
|
vm.primary_ip4 = IPAddress.objects.get_or_create(
|
|
@@ -137,15 +141,29 @@ def build_vm_full(name, ip_octet=1):
|
|
|
137
141
|
return vm
|
|
138
142
|
|
|
139
143
|
|
|
144
|
+
def expected_service_display(name, protocol, port):
|
|
145
|
+
"""Netbox 4.7 reduced Service.__str__ to the bare name; older releases
|
|
146
|
+
append the protocol and port list."""
|
|
147
|
+
if hasattr(Service, "port_mappings"):
|
|
148
|
+
return name
|
|
149
|
+
return f"{name} ({protocol}/{port})"
|
|
150
|
+
|
|
151
|
+
|
|
140
152
|
def build_service_for(parent, **kwargs):
|
|
141
153
|
"""Create a service bound to parent, with the parent's primary IPv4 attached.
|
|
142
154
|
|
|
143
155
|
Netbox 4.3 replaced Service.device/Service.virtual_machine with a single
|
|
144
156
|
generic `parent` relation, which is read-only on older releases.
|
|
157
|
+
|
|
158
|
+
Netbox 4.7 replaced Service.protocol/Service.ports with `port_mappings`
|
|
159
|
+
and rejects the legacy pair at the ORM level.
|
|
145
160
|
"""
|
|
146
|
-
|
|
161
|
+
if hasattr(Service, "port_mappings") and "ports" in kwargs:
|
|
162
|
+
protocol = kwargs.pop("protocol", "tcp")
|
|
163
|
+
kwargs["port_mappings"] = [f"{protocol}/{port}" for port in kwargs.pop("ports")]
|
|
164
|
+
try: # NetBox 4.3+
|
|
147
165
|
service = Service.objects.create(parent=parent, **kwargs)
|
|
148
|
-
except AttributeError:
|
|
166
|
+
except AttributeError: # NetBox <4.3
|
|
149
167
|
field = "device" if isinstance(parent, Device) else "virtual_machine"
|
|
150
168
|
service = Service.objects.create(**{field: parent}, **kwargs)
|
|
151
169
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "netbox-plugin-prometheus-sd"
|
|
3
|
-
version = "
|
|
3
|
+
version = "2.2.0" # placeholder
|
|
4
4
|
description = "A Netbox plugin to provide Netbox entires to Prometheus HTTP service discovery"
|
|
5
5
|
authors = ["Felix Peters <felix.peters@breuninger.de>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: netbox-plugin-prometheus-sd
|
|
3
|
-
Version: 2.0.0
|
|
4
|
-
Summary: A Netbox plugin to provide Netbox entires to Prometheus HTTP service discovery
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: Felix Peters
|
|
7
|
-
Author-email: felix.peters@breuninger.de
|
|
8
|
-
Requires-Python: >=3.10,<4.0
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
|
|
17
|
-
# netbox-plugin-prometheus-sd
|
|
18
|
-
|
|
19
|
-
[](https://opensource.org/licenses/MIT)
|
|
20
|
-
[](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
|
|
21
|
-
[](https://pypi.org/project/netbox-plugin-prometheus-sd/)
|
|
22
|
-
|
|
23
|
-
[](https://www.buymeacoffee.com/flxpeters)
|
|
24
|
-
|
|
25
|
-
Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
|
|
26
|
-
|
|
27
|
-
HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
|
|
28
|
-
This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
|
|
29
|
-
|
|
30
|
-
## Compatibility
|
|
31
|
-
|
|
32
|
-
We aim to support the latest major versions of Netbox.
|
|
33
|
-
For now we support Netbox `>= 4.0` including bugfix versions. Older versions may work, but without any guarantee.
|
|
34
|
-
|
|
35
|
-
Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
|
|
36
|
-
Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
|
|
37
|
-
|
|
38
|
-
## Installation
|
|
39
|
-
|
|
40
|
-
The plugin is available as a Python package in pypi and can be installed with pip
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
pip install netbox-plugin-prometheus-sd
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
|
|
47
|
-
|
|
48
|
-
```python
|
|
49
|
-
PLUGINS = ['netbox_prometheus_sd']
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
The plugin has not further plugin configuration.
|
|
53
|
-
|
|
54
|
-
## Usage
|
|
55
|
-
|
|
56
|
-
The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
|
|
57
|
-
|
|
58
|
-
### API
|
|
59
|
-
|
|
60
|
-
The plugin reuses Netbox API view sets with new serializers for Prometheus.
|
|
61
|
-
This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
|
|
62
|
-
Paging is disabled because Prometheus does not support paged results.
|
|
63
|
-
|
|
64
|
-
The plugin also reuses the Netbox authentication and permission model.
|
|
65
|
-
Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
|
|
69
|
-
GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
|
|
70
|
-
GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
|
|
71
|
-
GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
#### Extended services filters
|
|
75
|
-
|
|
76
|
-
Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>` parameters.
|
|
77
|
-
The lookup is only executed against the `tenant` attribute of the object associated with the service.
|
|
78
|
-
|
|
79
|
-
### Config context
|
|
80
|
-
|
|
81
|
-
The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
|
|
82
|
-
If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
|
|
83
|
-
|
|
84
|
-
```yaml
|
|
85
|
-
prometheus-plugin-prometheus-sd:
|
|
86
|
-
- metrics_path: /not/metrics
|
|
87
|
-
port: 4242
|
|
88
|
-
scheme: https
|
|
89
|
-
- port: 4243
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
|
|
93
|
-
config and filtering each scenario by a specific tag for instance.
|
|
94
|
-
|
|
95
|
-
If there is only one entry you can also use this form:
|
|
96
|
-
|
|
97
|
-
```yaml
|
|
98
|
-
prometheus-plugin-prometheus-sd:
|
|
99
|
-
metrics_path: /not/metrics
|
|
100
|
-
port: 4242
|
|
101
|
-
scheme: https
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Example
|
|
105
|
-
|
|
106
|
-
A working example on how to use this plugin with Prometheus is located at the `example` folder.
|
|
107
|
-
Netbox content is created by using Netbox docker initializers.
|
|
108
|
-
|
|
109
|
-
The demo data doesn't make sense, but they are good enough for demonstrating how to configure Prometheus
|
|
110
|
-
and get demo data to Prometheus service discovery.
|
|
111
|
-
|
|
112
|
-
Go to the `example` folder and run `docker-compose up --build`. Prometheus should get available on `http://localhost:9090`.
|
|
113
|
-
|
|
114
|
-
Push some example devices and objects to Netbox using the initializers:
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
docker-compose exec netbox /opt/netbox/netbox/manage.py load_initializer_data --path /opt/netbox/initializers
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Netbox content should then be available in the service discovery tab.
|
|
121
|
-
|
|
122
|
-
## Development
|
|
123
|
-
|
|
124
|
-
We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
|
|
125
|
-
To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
|
|
126
|
-
which creates "throwaway, lightweight" Netbox Docker containers.
|
|
127
|
-
|
|
128
|
-
All code to run in docker is located under `develop`.
|
|
129
|
-
To start a virtual env managed by poetry run `poetry shell`.
|
|
130
|
-
All following commands are started inside this environment.
|
|
131
|
-
|
|
132
|
-
In order to run tests invoke the tests:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Build the containers and execute all tests
|
|
136
|
-
invoke test
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
Features should be covered by a unit test, but some times it's easier to develop on an running system.
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
# Start a development environment
|
|
143
|
-
invoke build_dev
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Visit http://localhost:8000 and log in with the default admin credentials.
|
|
147
|
-
You can now define Netbox entities and test around.
|
|
148
|
-
|
|
149
|
-
API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
|
|
150
|
-
|
|
151
|
-
## Conventional Commits
|
|
152
|
-
|
|
153
|
-
This repository follows the Conventional Commits specification for versioning and changelog generation.
|
|
154
|
-
Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
|
|
155
|
-
about the changes made. Each commit message follows a defined format that includes a type,
|
|
156
|
-
an optional scope, and a message. The types typically include features, fixes, documentation, and more.
|
|
157
|
-
By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
|
|
158
|
-
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# netbox-plugin-prometheus-sd
|
|
2
|
-
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
-
[](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
|
|
5
|
-
[](https://pypi.org/project/netbox-plugin-prometheus-sd/)
|
|
6
|
-
|
|
7
|
-
[](https://www.buymeacoffee.com/flxpeters)
|
|
8
|
-
|
|
9
|
-
Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
|
|
10
|
-
|
|
11
|
-
HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
|
|
12
|
-
This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
|
|
13
|
-
|
|
14
|
-
## Compatibility
|
|
15
|
-
|
|
16
|
-
We aim to support the latest major versions of Netbox.
|
|
17
|
-
For now we support Netbox `>= 4.0` including bugfix versions. Older versions may work, but without any guarantee.
|
|
18
|
-
|
|
19
|
-
Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
|
|
20
|
-
Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
|
|
21
|
-
|
|
22
|
-
## Installation
|
|
23
|
-
|
|
24
|
-
The plugin is available as a Python package in pypi and can be installed with pip
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
pip install netbox-plugin-prometheus-sd
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
|
|
31
|
-
|
|
32
|
-
```python
|
|
33
|
-
PLUGINS = ['netbox_prometheus_sd']
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The plugin has not further plugin configuration.
|
|
37
|
-
|
|
38
|
-
## Usage
|
|
39
|
-
|
|
40
|
-
The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
|
|
41
|
-
|
|
42
|
-
### API
|
|
43
|
-
|
|
44
|
-
The plugin reuses Netbox API view sets with new serializers for Prometheus.
|
|
45
|
-
This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
|
|
46
|
-
Paging is disabled because Prometheus does not support paged results.
|
|
47
|
-
|
|
48
|
-
The plugin also reuses the Netbox authentication and permission model.
|
|
49
|
-
Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
|
|
53
|
-
GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
|
|
54
|
-
GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
|
|
55
|
-
GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
#### Extended services filters
|
|
59
|
-
|
|
60
|
-
Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>` parameters.
|
|
61
|
-
The lookup is only executed against the `tenant` attribute of the object associated with the service.
|
|
62
|
-
|
|
63
|
-
### Config context
|
|
64
|
-
|
|
65
|
-
The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
|
|
66
|
-
If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
|
|
67
|
-
|
|
68
|
-
```yaml
|
|
69
|
-
prometheus-plugin-prometheus-sd:
|
|
70
|
-
- metrics_path: /not/metrics
|
|
71
|
-
port: 4242
|
|
72
|
-
scheme: https
|
|
73
|
-
- port: 4243
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
|
|
77
|
-
config and filtering each scenario by a specific tag for instance.
|
|
78
|
-
|
|
79
|
-
If there is only one entry you can also use this form:
|
|
80
|
-
|
|
81
|
-
```yaml
|
|
82
|
-
prometheus-plugin-prometheus-sd:
|
|
83
|
-
metrics_path: /not/metrics
|
|
84
|
-
port: 4242
|
|
85
|
-
scheme: https
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Example
|
|
89
|
-
|
|
90
|
-
A working example on how to use this plugin with Prometheus is located at the `example` folder.
|
|
91
|
-
Netbox content is created by using Netbox docker initializers.
|
|
92
|
-
|
|
93
|
-
The demo data doesn't make sense, but they are good enough for demonstrating how to configure Prometheus
|
|
94
|
-
and get demo data to Prometheus service discovery.
|
|
95
|
-
|
|
96
|
-
Go to the `example` folder and run `docker-compose up --build`. Prometheus should get available on `http://localhost:9090`.
|
|
97
|
-
|
|
98
|
-
Push some example devices and objects to Netbox using the initializers:
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
docker-compose exec netbox /opt/netbox/netbox/manage.py load_initializer_data --path /opt/netbox/initializers
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Netbox content should then be available in the service discovery tab.
|
|
105
|
-
|
|
106
|
-
## Development
|
|
107
|
-
|
|
108
|
-
We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
|
|
109
|
-
To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
|
|
110
|
-
which creates "throwaway, lightweight" Netbox Docker containers.
|
|
111
|
-
|
|
112
|
-
All code to run in docker is located under `develop`.
|
|
113
|
-
To start a virtual env managed by poetry run `poetry shell`.
|
|
114
|
-
All following commands are started inside this environment.
|
|
115
|
-
|
|
116
|
-
In order to run tests invoke the tests:
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
# Build the containers and execute all tests
|
|
120
|
-
invoke test
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Features should be covered by a unit test, but some times it's easier to develop on an running system.
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
# Start a development environment
|
|
127
|
-
invoke build_dev
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Visit http://localhost:8000 and log in with the default admin credentials.
|
|
131
|
-
You can now define Netbox entities and test around.
|
|
132
|
-
|
|
133
|
-
API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
|
|
134
|
-
|
|
135
|
-
## Conventional Commits
|
|
136
|
-
|
|
137
|
-
This repository follows the Conventional Commits specification for versioning and changelog generation.
|
|
138
|
-
Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
|
|
139
|
-
about the changes made. Each commit message follows a defined format that includes a type,
|
|
140
|
-
an optional scope, and a message. The types typically include features, fixes, documentation, and more.
|
|
141
|
-
By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
from django.test import TestCase
|
|
2
|
-
|
|
3
|
-
from ipam.models import Service
|
|
4
|
-
from tenancy.models import Tenant
|
|
5
|
-
from utilities.testing import ChangeLoggedFilterSetTests
|
|
6
|
-
|
|
7
|
-
from . import utils
|
|
8
|
-
from ..filtersets import ServiceFilterSet
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|
12
|
-
queryset = Service.objects.all()
|
|
13
|
-
filterset = ServiceFilterSet
|
|
14
|
-
|
|
15
|
-
@classmethod
|
|
16
|
-
def setUpTestData(cls):
|
|
17
|
-
"""Netbox requires us to define test data in this method, otherwise the ORM won't pick them."""
|
|
18
|
-
for i in range(1, 4):
|
|
19
|
-
utils.build_device_full(f"firewall-full-0{i}", i)
|
|
20
|
-
utils.build_vm_full(f"vm-full-0{i}.example.com", i)
|
|
21
|
-
|
|
22
|
-
def test_device_tenant(self):
|
|
23
|
-
tenant = Tenant.objects.all()[0]
|
|
24
|
-
|
|
25
|
-
params = {"tenant_id": [tenant.pk]}
|
|
26
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
27
|
-
params = {"tenant": [tenant.slug]}
|
|
28
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
29
|
-
|
|
30
|
-
def test_vm_tenant(self):
|
|
31
|
-
tenant = Tenant.objects.all()[0]
|
|
32
|
-
|
|
33
|
-
params = {"tenant_id": [tenant.pk]}
|
|
34
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
35
|
-
params = {"tenant": [tenant.slug]}
|
|
36
|
-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|