opennms-api-wrapper 0.2.0__py3-none-any.whl
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.
- opennms_api_wrapper/__init__.py +10 -0
- opennms_api_wrapper/_acks.py +52 -0
- opennms_api_wrapper/_alarm_history.py +30 -0
- opennms_api_wrapper/_alarm_stats.py +24 -0
- opennms_api_wrapper/_alarms.py +130 -0
- opennms_api_wrapper/_base.py +104 -0
- opennms_api_wrapper/_business_services.py +42 -0
- opennms_api_wrapper/_categories.py +73 -0
- opennms_api_wrapper/_device_config.py +102 -0
- opennms_api_wrapper/_discovery.py +48 -0
- opennms_api_wrapper/_events.py +83 -0
- opennms_api_wrapper/_flows.py +235 -0
- opennms_api_wrapper/_foreign_sources.py +123 -0
- opennms_api_wrapper/_graphs.py +65 -0
- opennms_api_wrapper/_groups.py +69 -0
- opennms_api_wrapper/_heatmap.py +63 -0
- opennms_api_wrapper/_info.py +12 -0
- opennms_api_wrapper/_ipinterfaces_v2.py +22 -0
- opennms_api_wrapper/_ksc_reports.py +49 -0
- opennms_api_wrapper/_maps.py +36 -0
- opennms_api_wrapper/_measurements.py +75 -0
- opennms_api_wrapper/_metadata.py +207 -0
- opennms_api_wrapper/_nodes.py +301 -0
- opennms_api_wrapper/_notifications.py +35 -0
- opennms_api_wrapper/_outages.py +35 -0
- opennms_api_wrapper/_requisitions.py +249 -0
- opennms_api_wrapper/_resources.py +66 -0
- opennms_api_wrapper/_sched_outages.py +81 -0
- opennms_api_wrapper/_situations.py +81 -0
- opennms_api_wrapper/_snmp_config.py +31 -0
- opennms_api_wrapper/_snmpinterfaces_v2.py +22 -0
- opennms_api_wrapper/_users.py +53 -0
- opennms_api_wrapper/client.py +122 -0
- opennms_api_wrapper-0.2.0.dist-info/METADATA +232 -0
- opennms_api_wrapper-0.2.0.dist-info/RECORD +38 -0
- opennms_api_wrapper-0.2.0.dist-info/WHEEL +5 -0
- opennms_api_wrapper-0.2.0.dist-info/licenses/LICENSE +21 -0
- opennms_api_wrapper-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""opennms_api_wrapper – a thin Python wrapper for the OpenNMS REST API."""
|
|
2
|
+
from .client import OpenNMS
|
|
3
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
4
|
+
|
|
5
|
+
__all__ = ["OpenNMS"]
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("opennms-api-wrapper")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "unknown"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Acknowledgements REST API – /rest/acks."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AcksMixin:
|
|
5
|
+
def get_acks(self, limit: int = 10, offset: int = 0, **filters):
|
|
6
|
+
"""List acknowledgements.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
limit: Max number of results to return. Use ``0`` for all.
|
|
10
|
+
offset: Zero-based offset for pagination.
|
|
11
|
+
**filters: Additional Hibernate query filters passed directly as query parameters (e.g. ``severity="MAJOR"``).
|
|
12
|
+
"""
|
|
13
|
+
params = {"limit": limit, "offset": offset}
|
|
14
|
+
params.update(filters)
|
|
15
|
+
return self._get("acks", params=params)
|
|
16
|
+
|
|
17
|
+
def get_ack(self, ack_id: int):
|
|
18
|
+
"""Return the acknowledgement with the given ID."""
|
|
19
|
+
return self._get(f"acks/{ack_id}")
|
|
20
|
+
|
|
21
|
+
def get_ack_count(self) -> int:
|
|
22
|
+
"""Return the total number of acknowledgements."""
|
|
23
|
+
return self._get("acks/count")
|
|
24
|
+
|
|
25
|
+
# Write (form-encoded POST)
|
|
26
|
+
|
|
27
|
+
def create_ack(self, action: str, alarm_id: int = None,
|
|
28
|
+
notification_id: int = None):
|
|
29
|
+
"""Create or modify an acknowledgement.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
action: One of ``"ack"``, ``"unack"``, ``"clear"``, ``"esc"``.
|
|
33
|
+
alarm_id: Target alarm ID (mutually exclusive with
|
|
34
|
+
notification_id).
|
|
35
|
+
notification_id: Target notification ID.
|
|
36
|
+
"""
|
|
37
|
+
data = {"action": action}
|
|
38
|
+
if alarm_id is not None:
|
|
39
|
+
data["alarmId"] = alarm_id
|
|
40
|
+
if notification_id is not None:
|
|
41
|
+
data["notifId"] = notification_id
|
|
42
|
+
return self._post("acks", form_data=data)
|
|
43
|
+
|
|
44
|
+
# Convenience wrappers
|
|
45
|
+
|
|
46
|
+
def ack_notification(self, notification_id: int):
|
|
47
|
+
"""Acknowledge notification *notification_id*."""
|
|
48
|
+
return self.create_ack("ack", notification_id=notification_id)
|
|
49
|
+
|
|
50
|
+
def unack_notification(self, notification_id: int):
|
|
51
|
+
"""Remove acknowledgement from notification *notification_id*."""
|
|
52
|
+
return self.create_ack("unack", notification_id=notification_id)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Alarm History REST API – /rest/alarms/history."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AlarmHistoryMixin:
|
|
5
|
+
def get_alarm_history(self, at: int = None):
|
|
6
|
+
"""Return last known state of all active alarms.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
at: Optional millisecond epoch timestamp for historical lookup.
|
|
10
|
+
"""
|
|
11
|
+
params = {}
|
|
12
|
+
if at is not None:
|
|
13
|
+
params["at"] = at
|
|
14
|
+
return self._get("alarms/history", params=params or None)
|
|
15
|
+
|
|
16
|
+
def get_alarm_history_at(self, alarm_id: int, at: int = None):
|
|
17
|
+
"""Return final known state of *alarm_id* (optionally at a point in time).
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
alarm_id: The alarm database ID.
|
|
21
|
+
at: Optional millisecond epoch timestamp.
|
|
22
|
+
"""
|
|
23
|
+
params = {}
|
|
24
|
+
if at is not None:
|
|
25
|
+
params["at"] = at
|
|
26
|
+
return self._get(f"alarms/history/{alarm_id}", params=params or None)
|
|
27
|
+
|
|
28
|
+
def get_alarm_history_states(self, alarm_id: int):
|
|
29
|
+
"""Return all state transitions for *alarm_id*."""
|
|
30
|
+
return self._get(f"alarms/history/{alarm_id}/states")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Alarm Statistics REST API – /rest/stats/alarms."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AlarmStatsMixin:
|
|
5
|
+
def get_alarm_stats(self, **filters):
|
|
6
|
+
"""Return alarm statistics.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
**filters: Additional Hibernate query filters passed directly as query parameters (e.g. ``severity="MAJOR"``).
|
|
10
|
+
Supports the same filter parameters as ``get_alarms()``.
|
|
11
|
+
"""
|
|
12
|
+
return self._get("stats/alarms", params=filters or None)
|
|
13
|
+
|
|
14
|
+
def get_alarm_stats_by_severity(self, severities: list = None):
|
|
15
|
+
"""Return alarm statistics grouped by severity.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
severities: Optional list of severity strings to include,
|
|
19
|
+
e.g. ``["MAJOR", "CRITICAL"]``.
|
|
20
|
+
"""
|
|
21
|
+
params = {}
|
|
22
|
+
if severities:
|
|
23
|
+
params["severities"] = ",".join(severities)
|
|
24
|
+
return self._get("stats/alarms/by-severity", params=params or None)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Alarms REST API – /rest/alarms and /api/v2/alarms."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AlarmsMixin:
|
|
5
|
+
def get_alarms(self, limit: int = 10, offset: int = 0,
|
|
6
|
+
order_by: str = None, order: str = None, **filters):
|
|
7
|
+
"""List alarms (v1).
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
limit: Max number of results to return. Use ``0`` for all.
|
|
11
|
+
offset: Zero-based offset for pagination.
|
|
12
|
+
order_by: Field name to sort by.
|
|
13
|
+
order: Sort direction: ``"asc"`` or ``"desc"``.
|
|
14
|
+
**filters: Additional Hibernate query filters passed directly as
|
|
15
|
+
query parameters (e.g. ``severity="MAJOR"``). Pass
|
|
16
|
+
``comparator`` to change the match type (eq/ilike/…).
|
|
17
|
+
"""
|
|
18
|
+
params = {"limit": limit, "offset": offset}
|
|
19
|
+
if order_by:
|
|
20
|
+
params["orderBy"] = order_by
|
|
21
|
+
if order:
|
|
22
|
+
params["order"] = order
|
|
23
|
+
params.update(filters)
|
|
24
|
+
return self._get("alarms", params=params)
|
|
25
|
+
|
|
26
|
+
def get_alarm(self, alarm_id: int):
|
|
27
|
+
"""Return the alarm with the given ID."""
|
|
28
|
+
return self._get(f"alarms/{alarm_id}")
|
|
29
|
+
|
|
30
|
+
def get_alarm_count(self) -> int:
|
|
31
|
+
"""Return the total number of alarms."""
|
|
32
|
+
return self._get("alarms/count")
|
|
33
|
+
|
|
34
|
+
# Single-alarm actions (v1 PUT with query params)
|
|
35
|
+
|
|
36
|
+
def ack_alarm(self, alarm_id: int, ack_user: str = None):
|
|
37
|
+
"""Acknowledge alarm *alarm_id*.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
ack_user: Acknowledge on behalf of this user (requires admin role).
|
|
41
|
+
"""
|
|
42
|
+
params = {"ack": "true"}
|
|
43
|
+
if ack_user:
|
|
44
|
+
params["ackUser"] = ack_user
|
|
45
|
+
return self._put(f"alarms/{alarm_id}", params=params)
|
|
46
|
+
|
|
47
|
+
def unack_alarm(self, alarm_id: int):
|
|
48
|
+
"""Remove acknowledgement from alarm *alarm_id*."""
|
|
49
|
+
return self._put(f"alarms/{alarm_id}", params={"ack": "false"})
|
|
50
|
+
|
|
51
|
+
def clear_alarm(self, alarm_id: int):
|
|
52
|
+
"""Clear alarm *alarm_id* (sets severity to CLEARED)."""
|
|
53
|
+
return self._put(f"alarms/{alarm_id}", params={"clear": "true"})
|
|
54
|
+
|
|
55
|
+
def escalate_alarm(self, alarm_id: int):
|
|
56
|
+
"""Escalate the severity of alarm *alarm_id* by one step."""
|
|
57
|
+
return self._put(f"alarms/{alarm_id}", params={"escalate": "true"})
|
|
58
|
+
|
|
59
|
+
# Bulk alarm actions
|
|
60
|
+
|
|
61
|
+
def bulk_ack_alarms(self, **filters):
|
|
62
|
+
"""Acknowledge all alarms matching the given filters.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
**filters: Additional Hibernate query filters passed directly as query parameters (e.g. ``severity="MAJOR"``).
|
|
66
|
+
"""
|
|
67
|
+
params = {"ack": "true"}
|
|
68
|
+
params.update(filters)
|
|
69
|
+
return self._put("alarms", params=params)
|
|
70
|
+
|
|
71
|
+
def bulk_unack_alarms(self, **filters):
|
|
72
|
+
"""Remove acknowledgement from all alarms matching the given filters.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
**filters: Additional Hibernate query filters passed directly as query parameters (e.g. ``severity="MAJOR"``).
|
|
76
|
+
"""
|
|
77
|
+
params = {"ack": "false"}
|
|
78
|
+
params.update(filters)
|
|
79
|
+
return self._put("alarms", params=params)
|
|
80
|
+
|
|
81
|
+
def bulk_clear_alarms(self, **filters):
|
|
82
|
+
"""Clear all alarms matching the given filters.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
**filters: Additional Hibernate query filters passed directly as query parameters (e.g. ``severity="MAJOR"``).
|
|
86
|
+
"""
|
|
87
|
+
params = {"clear": "true"}
|
|
88
|
+
params.update(filters)
|
|
89
|
+
return self._put("alarms", params=params)
|
|
90
|
+
|
|
91
|
+
def bulk_escalate_alarms(self, **filters):
|
|
92
|
+
"""Escalate all alarms matching the given filters.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
**filters: Additional Hibernate query filters passed directly as query parameters (e.g. ``severity="MAJOR"``).
|
|
96
|
+
"""
|
|
97
|
+
params = {"escalate": "true"}
|
|
98
|
+
params.update(filters)
|
|
99
|
+
return self._put("alarms", params=params)
|
|
100
|
+
|
|
101
|
+
# v2 alarms (FIQL filtering)
|
|
102
|
+
|
|
103
|
+
def get_alarms_v2(self, fiql: str = None, limit: int = 10,
|
|
104
|
+
offset: int = 0, order_by: str = None,
|
|
105
|
+
order: str = None):
|
|
106
|
+
"""List alarms using the v2 API with optional FIQL filter string.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
fiql: FIQL filter string (e.g. ``"alarm.severity==MAJOR"``).
|
|
110
|
+
limit: Max number of results to return. Use ``0`` for all.
|
|
111
|
+
offset: Zero-based offset for pagination.
|
|
112
|
+
order_by: Field name to sort by.
|
|
113
|
+
order: Sort direction: ``"asc"`` or ``"desc"``.
|
|
114
|
+
|
|
115
|
+
Example::
|
|
116
|
+
|
|
117
|
+
client.get_alarms_v2(fiql="alarm.severity==MAJOR")
|
|
118
|
+
"""
|
|
119
|
+
params = {"limit": limit, "offset": offset}
|
|
120
|
+
if fiql:
|
|
121
|
+
params["_s"] = fiql
|
|
122
|
+
if order_by:
|
|
123
|
+
params["orderBy"] = order_by
|
|
124
|
+
if order:
|
|
125
|
+
params["order"] = order
|
|
126
|
+
return self._get("alarms", params=params, v2=True)
|
|
127
|
+
|
|
128
|
+
def get_alarm_v2(self, alarm_id: int):
|
|
129
|
+
"""Return a single alarm by ID using the v2 API."""
|
|
130
|
+
return self._get(f"alarms/{alarm_id}", v2=True)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Base HTTP client for the OpenNMS REST API."""
|
|
2
|
+
import requests
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class _OpenNMSBase:
|
|
6
|
+
"""Base class providing authenticated HTTP helpers."""
|
|
7
|
+
|
|
8
|
+
def __init__(self, url: str, username: str, password: str,
|
|
9
|
+
verify_ssl: bool = True, timeout: int = 30):
|
|
10
|
+
"""Initialize base URLs, credentials, and a shared requests session.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
url: Base URL of the OpenNMS server (e.g. ``"https://onms.example.com"``).
|
|
14
|
+
username: OpenNMS username for HTTP Basic authentication.
|
|
15
|
+
password: Password for HTTP Basic authentication.
|
|
16
|
+
verify_ssl: When ``False`` SSL certificate verification is
|
|
17
|
+
disabled. Defaults to ``True``.
|
|
18
|
+
timeout: Socket timeout in seconds for all HTTP requests.
|
|
19
|
+
Defaults to ``30``. Pass ``None`` to disable.
|
|
20
|
+
"""
|
|
21
|
+
base = url.rstrip("/")
|
|
22
|
+
self._v1_url = f"{base}/opennms/rest"
|
|
23
|
+
self._v2_url = f"{base}/opennms/api/v2"
|
|
24
|
+
self._timeout = timeout
|
|
25
|
+
self._session = requests.Session()
|
|
26
|
+
self._session.auth = (username, password)
|
|
27
|
+
self._session.headers.update({
|
|
28
|
+
"Accept": "application/json, text/plain;q=0.9",
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
})
|
|
31
|
+
self._session.verify = verify_ssl
|
|
32
|
+
|
|
33
|
+
def _url(self, path: str, v2: bool = False) -> str:
|
|
34
|
+
"""Build a full endpoint URL, using the v2 base if *v2* is True."""
|
|
35
|
+
base = self._v2_url if v2 else self._v1_url
|
|
36
|
+
return f"{base}/{path.lstrip('/')}"
|
|
37
|
+
|
|
38
|
+
def _parse(self, resp: requests.Response):
|
|
39
|
+
"""Parse an HTTP response into a Python object.
|
|
40
|
+
|
|
41
|
+
Returns a dict/list for JSON, int or str for text/plain, and None
|
|
42
|
+
for empty 204 responses. Raises ``requests.exceptions.HTTPError``
|
|
43
|
+
on non-2xx status codes.
|
|
44
|
+
"""
|
|
45
|
+
resp.raise_for_status()
|
|
46
|
+
if not resp.content:
|
|
47
|
+
return None
|
|
48
|
+
ct = resp.headers.get("Content-Type", "")
|
|
49
|
+
if "application/json" in ct:
|
|
50
|
+
return resp.json()
|
|
51
|
+
if "text/plain" in ct:
|
|
52
|
+
text = resp.text.strip()
|
|
53
|
+
try:
|
|
54
|
+
return int(text)
|
|
55
|
+
except ValueError:
|
|
56
|
+
return text
|
|
57
|
+
try:
|
|
58
|
+
return resp.json()
|
|
59
|
+
except Exception:
|
|
60
|
+
return resp.text
|
|
61
|
+
|
|
62
|
+
def _get(self, path: str, params: dict = None, v2: bool = False):
|
|
63
|
+
"""Send a GET request and return the parsed response."""
|
|
64
|
+
resp = self._session.get(self._url(path, v2), params=params,
|
|
65
|
+
timeout=self._timeout)
|
|
66
|
+
return self._parse(resp)
|
|
67
|
+
|
|
68
|
+
def _post(self, path: str, json_data=None, form_data: dict = None,
|
|
69
|
+
params: dict = None, v2: bool = False):
|
|
70
|
+
"""Send a POST request and return the parsed response.
|
|
71
|
+
|
|
72
|
+
Sends form-encoded data when *form_data* is provided, otherwise JSON.
|
|
73
|
+
"""
|
|
74
|
+
url = self._url(path, v2)
|
|
75
|
+
if form_data is not None:
|
|
76
|
+
resp = self._session.post(url, data=form_data, params=params,
|
|
77
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
78
|
+
timeout=self._timeout)
|
|
79
|
+
else:
|
|
80
|
+
resp = self._session.post(url, json=json_data, params=params,
|
|
81
|
+
timeout=self._timeout)
|
|
82
|
+
return self._parse(resp)
|
|
83
|
+
|
|
84
|
+
def _put(self, path: str, json_data=None, form_data: dict = None,
|
|
85
|
+
params: dict = None, v2: bool = False):
|
|
86
|
+
"""Send a PUT request and return the parsed response.
|
|
87
|
+
|
|
88
|
+
Sends form-encoded data when *form_data* is provided, otherwise JSON.
|
|
89
|
+
"""
|
|
90
|
+
url = self._url(path, v2)
|
|
91
|
+
if form_data is not None:
|
|
92
|
+
resp = self._session.put(url, data=form_data, params=params,
|
|
93
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
94
|
+
timeout=self._timeout)
|
|
95
|
+
else:
|
|
96
|
+
resp = self._session.put(url, json=json_data, params=params,
|
|
97
|
+
timeout=self._timeout)
|
|
98
|
+
return self._parse(resp)
|
|
99
|
+
|
|
100
|
+
def _delete(self, path: str, params: dict = None, v2: bool = False):
|
|
101
|
+
"""Send a DELETE request and return the parsed response."""
|
|
102
|
+
resp = self._session.delete(self._url(path, v2), params=params,
|
|
103
|
+
timeout=self._timeout)
|
|
104
|
+
return self._parse(resp)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Business Service Monitoring REST API v2 – /api/v2/business-services."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BusinessServicesMixin:
|
|
5
|
+
def get_business_services(self):
|
|
6
|
+
"""List all business services."""
|
|
7
|
+
return self._get("business-services", v2=True)
|
|
8
|
+
|
|
9
|
+
def get_business_service(self, service_id: int):
|
|
10
|
+
"""Get a specific business service by *service_id*."""
|
|
11
|
+
return self._get(f"business-services/{service_id}", v2=True)
|
|
12
|
+
|
|
13
|
+
def create_business_service(self, service: dict):
|
|
14
|
+
"""Create a new business service.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
service: Business service definition dict. Common keys:
|
|
18
|
+
``name`` (str), ``attributes`` (dict of key/value pairs),
|
|
19
|
+
``reduceFunction`` (dict with a ``type`` key),
|
|
20
|
+
``edges`` (list of edge configuration dicts). Example::
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
"name": "My App",
|
|
24
|
+
"attributes": {"dc": "us-east-1"},
|
|
25
|
+
"reduceFunction": {"type": "HighestSeverity"},
|
|
26
|
+
}
|
|
27
|
+
"""
|
|
28
|
+
return self._post("business-services", json_data=service, v2=True)
|
|
29
|
+
|
|
30
|
+
def update_business_service(self, service_id: int, service: dict):
|
|
31
|
+
"""Update a business service.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
service_id: Database ID of the business service to update.
|
|
35
|
+
service: Updated business service definition dict.
|
|
36
|
+
"""
|
|
37
|
+
return self._put(f"business-services/{service_id}", json_data=service,
|
|
38
|
+
v2=True)
|
|
39
|
+
|
|
40
|
+
def delete_business_service(self, service_id: int):
|
|
41
|
+
"""Delete a business service."""
|
|
42
|
+
return self._delete(f"business-services/{service_id}", v2=True)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Categories REST API – /rest/categories."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CategoriesMixin:
|
|
5
|
+
# ==================================================================
|
|
6
|
+
# Categories CRUD
|
|
7
|
+
# ==================================================================
|
|
8
|
+
|
|
9
|
+
def get_categories(self):
|
|
10
|
+
"""List all configured surveillance categories."""
|
|
11
|
+
return self._get("categories")
|
|
12
|
+
|
|
13
|
+
def get_category(self, category: str):
|
|
14
|
+
"""Get a specific category by *category* name."""
|
|
15
|
+
return self._get(f"categories/{category}")
|
|
16
|
+
|
|
17
|
+
def create_category(self, category: dict):
|
|
18
|
+
"""Add a new surveillance category.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
category: Category dict. Example:
|
|
22
|
+
``{"name": "Production", "authorizedGroups": []}``
|
|
23
|
+
"""
|
|
24
|
+
return self._post("categories", json_data=category)
|
|
25
|
+
|
|
26
|
+
def update_category(self, category: str, data: dict):
|
|
27
|
+
"""Update a category.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
category: Category name to update.
|
|
31
|
+
data: Dict of category fields to change.
|
|
32
|
+
"""
|
|
33
|
+
return self._put(f"categories/{category}", json_data=data)
|
|
34
|
+
|
|
35
|
+
def delete_category(self, category: str):
|
|
36
|
+
"""Delete a category."""
|
|
37
|
+
return self._delete(f"categories/{category}")
|
|
38
|
+
|
|
39
|
+
# ==================================================================
|
|
40
|
+
# Category ↔ Node associations
|
|
41
|
+
# ==================================================================
|
|
42
|
+
|
|
43
|
+
def get_node_categories_list(self, node_id):
|
|
44
|
+
"""Get all categories for *node_id*."""
|
|
45
|
+
return self._get(f"categories/nodes/{node_id}")
|
|
46
|
+
|
|
47
|
+
def get_category_for_node(self, category: str, node_id):
|
|
48
|
+
"""Get a specific category for *node_id*."""
|
|
49
|
+
return self._get(f"categories/{category}/nodes/{node_id}")
|
|
50
|
+
|
|
51
|
+
def associate_category_with_node(self, category: str, node_id):
|
|
52
|
+
"""Associate *category* with *node_id*."""
|
|
53
|
+
return self._put(f"categories/{category}/nodes/{node_id}")
|
|
54
|
+
|
|
55
|
+
def dissociate_category_from_node(self, category: str, node_id):
|
|
56
|
+
"""Remove *category* from *node_id*."""
|
|
57
|
+
return self._delete(f"categories/{category}/nodes/{node_id}")
|
|
58
|
+
|
|
59
|
+
# ==================================================================
|
|
60
|
+
# Category ↔ Group associations
|
|
61
|
+
# ==================================================================
|
|
62
|
+
|
|
63
|
+
def get_categories_for_group(self, group: str):
|
|
64
|
+
"""Get categories associated with user group *group*."""
|
|
65
|
+
return self._get(f"categories/groups/{group}")
|
|
66
|
+
|
|
67
|
+
def associate_category_with_group(self, category: str, group: str):
|
|
68
|
+
"""Associate *category* with user group *group*."""
|
|
69
|
+
return self._put(f"categories/{category}/groups/{group}")
|
|
70
|
+
|
|
71
|
+
def dissociate_category_from_group(self, category: str, group: str):
|
|
72
|
+
"""Remove *category* from user group *group*."""
|
|
73
|
+
return self._delete(f"categories/{category}/groups/{group}")
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Device Configuration REST API – /rest/device-config."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DeviceConfigMixin:
|
|
5
|
+
def get_device_configs(self, limit: int = 10, offset: int = 0,
|
|
6
|
+
order_by: str = None, order: str = None,
|
|
7
|
+
device_name: str = None, ip_address: str = None,
|
|
8
|
+
config_type: str = None,
|
|
9
|
+
created_after: int = None,
|
|
10
|
+
created_before: int = None):
|
|
11
|
+
"""List all device configurations (sorted by lastUpdated by default).
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
limit: Max number of results to return. Use ``0`` for all.
|
|
15
|
+
offset: Zero-based offset for pagination.
|
|
16
|
+
order_by: Field name to sort by.
|
|
17
|
+
order: Sort direction: ``"asc"`` or ``"desc"``.
|
|
18
|
+
device_name: Filter by device hostname.
|
|
19
|
+
ip_address: Filter by IP address.
|
|
20
|
+
config_type: Filter by config type string.
|
|
21
|
+
created_after: Filter to configs created after this ms epoch.
|
|
22
|
+
created_before: Filter to configs created before this ms epoch.
|
|
23
|
+
"""
|
|
24
|
+
params: dict = {"limit": limit, "offset": offset}
|
|
25
|
+
if order_by:
|
|
26
|
+
params["orderBy"] = order_by
|
|
27
|
+
if order:
|
|
28
|
+
params["order"] = order
|
|
29
|
+
if device_name:
|
|
30
|
+
params["deviceName"] = device_name
|
|
31
|
+
if ip_address:
|
|
32
|
+
params["ipAddress"] = ip_address
|
|
33
|
+
if config_type:
|
|
34
|
+
params["configType"] = config_type
|
|
35
|
+
if created_after is not None:
|
|
36
|
+
params["createdAfter"] = created_after
|
|
37
|
+
if created_before is not None:
|
|
38
|
+
params["createdBefore"] = created_before
|
|
39
|
+
return self._get("device-config", params=params)
|
|
40
|
+
|
|
41
|
+
def get_device_config(self, config_id: int):
|
|
42
|
+
"""Get device configuration for a specific database *config_id*."""
|
|
43
|
+
return self._get(f"device-config/{config_id}")
|
|
44
|
+
|
|
45
|
+
def get_device_config_by_interface(self, interface_id: int):
|
|
46
|
+
"""Get all configs for a specific *interface_id*."""
|
|
47
|
+
return self._get(f"device-config/interface/{interface_id}")
|
|
48
|
+
|
|
49
|
+
def get_latest_device_configs(self, limit: int = 10, offset: int = 0,
|
|
50
|
+
order_by: str = None, order: str = None,
|
|
51
|
+
search: str = None, status: str = None):
|
|
52
|
+
"""Return the latest config for all devices.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
limit: Max number of results to return. Use ``0`` for all.
|
|
56
|
+
offset: Zero-based offset for pagination.
|
|
57
|
+
order_by: Field name to sort by.
|
|
58
|
+
order: Sort direction: ``"asc"`` or ``"desc"``.
|
|
59
|
+
search: Search term for device name / IP.
|
|
60
|
+
status: Filter by backup status.
|
|
61
|
+
"""
|
|
62
|
+
params: dict = {"limit": limit, "offset": offset}
|
|
63
|
+
if order_by:
|
|
64
|
+
params["orderBy"] = order_by
|
|
65
|
+
if order:
|
|
66
|
+
params["order"] = order
|
|
67
|
+
if search:
|
|
68
|
+
params["search"] = search
|
|
69
|
+
if status:
|
|
70
|
+
params["status"] = status
|
|
71
|
+
return self._get("device-config/latest", params=params)
|
|
72
|
+
|
|
73
|
+
def download_device_configs(self, config_ids: list):
|
|
74
|
+
"""Download configs for one or more *config_ids*.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
config_ids: List of integer config IDs to download.
|
|
78
|
+
"""
|
|
79
|
+
params = {"id": ",".join(str(i) for i in config_ids)}
|
|
80
|
+
return self._get("device-config/download", params=params)
|
|
81
|
+
|
|
82
|
+
def backup_device_config(self, backups: list):
|
|
83
|
+
"""Trigger a backup retrieval for one or more interfaces.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
backups: List of backup request dicts. Each dict has keys:
|
|
87
|
+
``ipAddress`` (str): Interface IP address.
|
|
88
|
+
``location`` (str): Monitoring location name (e.g.
|
|
89
|
+
``"Default"``). ``serviceName`` (str): Service name (e.g.
|
|
90
|
+
``"DeviceConfig-default"``). ``blocking`` (bool): Whether
|
|
91
|
+
to wait for the backup to complete. Example::
|
|
92
|
+
|
|
93
|
+
[
|
|
94
|
+
{
|
|
95
|
+
"ipAddress": "192.168.1.1",
|
|
96
|
+
"location": "Default",
|
|
97
|
+
"serviceName": "DeviceConfig-default",
|
|
98
|
+
"blocking": False,
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
"""
|
|
102
|
+
return self._post("device-config/backup", json_data=backups)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Discovery REST API v2 – /api/v2/discovery."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DiscoveryMixin:
|
|
5
|
+
def discover(self, config: dict):
|
|
6
|
+
"""Submit a one-time discovery scan configuration (v2).
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
config: Discovery configuration dict. Supported keys (all lists
|
|
10
|
+
default to empty):
|
|
11
|
+
|
|
12
|
+
- ``specifics`` (list): Individual IPs to scan. Each entry::
|
|
13
|
+
|
|
14
|
+
{"ip": "192.168.0.1", "location": "Default",
|
|
15
|
+
"retries": 1, "timeout": 2000, "foreignSource": "FS"}
|
|
16
|
+
|
|
17
|
+
- ``include_ranges`` (list): IP ranges to scan. Each entry::
|
|
18
|
+
|
|
19
|
+
{"begin": "192.168.0.1", "end": "192.168.0.254",
|
|
20
|
+
"location": "Default", "retries": 1, "timeout": 2000}
|
|
21
|
+
|
|
22
|
+
- ``exclude_ranges`` (list): IP ranges to exclude. Each entry::
|
|
23
|
+
|
|
24
|
+
{"begin": "192.168.0.100", "end": "192.168.0.110"}
|
|
25
|
+
|
|
26
|
+
- ``include_urls`` (list): URLs with newline-delimited IPs.
|
|
27
|
+
Each entry::
|
|
28
|
+
|
|
29
|
+
{"url": "http://example.com/ips.txt", "location": "Default"}
|
|
30
|
+
|
|
31
|
+
Note: The v2 discovery endpoint is documented as XML-only. This
|
|
32
|
+
method sends JSON; if your OpenNMS version rejects it with HTTP 415
|
|
33
|
+
please open an issue — the workaround is to submit the request
|
|
34
|
+
manually with an XML body matching the ``discovery-configuration``
|
|
35
|
+
schema.
|
|
36
|
+
|
|
37
|
+
Example::
|
|
38
|
+
|
|
39
|
+
client.discover({
|
|
40
|
+
"specifics": [
|
|
41
|
+
{"ip": "10.0.0.1", "location": "Default", "foreignSource": "Routers"}
|
|
42
|
+
],
|
|
43
|
+
"include_ranges": [
|
|
44
|
+
{"begin": "10.0.1.1", "end": "10.0.1.254"}
|
|
45
|
+
],
|
|
46
|
+
})
|
|
47
|
+
"""
|
|
48
|
+
return self._post("discovery", json_data=config, v2=True)
|