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.
Files changed (38) hide show
  1. opennms_api_wrapper/__init__.py +10 -0
  2. opennms_api_wrapper/_acks.py +52 -0
  3. opennms_api_wrapper/_alarm_history.py +30 -0
  4. opennms_api_wrapper/_alarm_stats.py +24 -0
  5. opennms_api_wrapper/_alarms.py +130 -0
  6. opennms_api_wrapper/_base.py +104 -0
  7. opennms_api_wrapper/_business_services.py +42 -0
  8. opennms_api_wrapper/_categories.py +73 -0
  9. opennms_api_wrapper/_device_config.py +102 -0
  10. opennms_api_wrapper/_discovery.py +48 -0
  11. opennms_api_wrapper/_events.py +83 -0
  12. opennms_api_wrapper/_flows.py +235 -0
  13. opennms_api_wrapper/_foreign_sources.py +123 -0
  14. opennms_api_wrapper/_graphs.py +65 -0
  15. opennms_api_wrapper/_groups.py +69 -0
  16. opennms_api_wrapper/_heatmap.py +63 -0
  17. opennms_api_wrapper/_info.py +12 -0
  18. opennms_api_wrapper/_ipinterfaces_v2.py +22 -0
  19. opennms_api_wrapper/_ksc_reports.py +49 -0
  20. opennms_api_wrapper/_maps.py +36 -0
  21. opennms_api_wrapper/_measurements.py +75 -0
  22. opennms_api_wrapper/_metadata.py +207 -0
  23. opennms_api_wrapper/_nodes.py +301 -0
  24. opennms_api_wrapper/_notifications.py +35 -0
  25. opennms_api_wrapper/_outages.py +35 -0
  26. opennms_api_wrapper/_requisitions.py +249 -0
  27. opennms_api_wrapper/_resources.py +66 -0
  28. opennms_api_wrapper/_sched_outages.py +81 -0
  29. opennms_api_wrapper/_situations.py +81 -0
  30. opennms_api_wrapper/_snmp_config.py +31 -0
  31. opennms_api_wrapper/_snmpinterfaces_v2.py +22 -0
  32. opennms_api_wrapper/_users.py +53 -0
  33. opennms_api_wrapper/client.py +122 -0
  34. opennms_api_wrapper-0.2.0.dist-info/METADATA +232 -0
  35. opennms_api_wrapper-0.2.0.dist-info/RECORD +38 -0
  36. opennms_api_wrapper-0.2.0.dist-info/WHEEL +5 -0
  37. opennms_api_wrapper-0.2.0.dist-info/licenses/LICENSE +21 -0
  38. 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)