Skip to content

Commit 21dea7e

Browse files
authored
Merge pull request #39 from sbarbett/health-checks
New Endpoints and Utilities - Issues# 31, 32, 33, 34, 35, 36, 37, 38 - v2.3.0
2 parents b133a94 + 7bbb818 commit 21dea7e

File tree

11 files changed

+658
-3
lines changed

11 files changed

+658
-3
lines changed

.plugin-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2.2.5
1+
v2.3.0

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,10 @@ export USERNAME='your_username'
269269
export PASSWORD='your_password'
270270
```
271271

272+
### Background Tasks
273+
274+
Utilities for handling long running tasks that process in the background, such as reports or exports, are available and documented [here](./src/ultra_rest_client/utils/README.md)
275+
272276
## Functionality
273277

274278
The sample code does not attempt to implement a client for all available UltraDNS REST API functionality. It provides access to basic functionality. Adding additional functionality should be relatively straightforward, and any contributions from the UltraDNS community would be greatly appreciated. See [sample.py](sample.py) for an example of how to use this library in your own code.

examples/report_handler_example.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python
2+
"""
3+
Example script demonstrating the use of the ReportHandler utility class.
4+
5+
This script shows how to use the ReportHandler to handle report generation API responses
6+
from the UltraDNS API.
7+
"""
8+
9+
from ultra_rest_client import RestApiClient, ReportHandler
10+
11+
# Initialize the client
12+
client = RestApiClient('your_username', 'your_password')
13+
14+
# Example 1: Creating an advanced NXDOMAIN report
15+
# --------------------------------------------
16+
# The endpoint returns a requestId that needs to be polled until the report is complete
17+
print("Example 1: Creating an advanced NXDOMAIN report")
18+
response = client.create_advanced_nxdomain_report(
19+
startDate='2023-01-01',
20+
endDate='2023-01-31',
21+
zoneNames=['example.com']
22+
)
23+
print("Initial response:", response)
24+
25+
# The ReportHandler will automatically poll until the report is complete
26+
report_result = ReportHandler(response, client)
27+
print("Final result after polling:")
28+
print(report_result) # This will print the final result
29+
30+
# Example 2: Creating a projected query volume report
31+
# --------------------------------------------
32+
print("\nExample 2: Creating a projected query volume report")
33+
response = client.create_projected_query_volume_report('your_account_name')
34+
print("Initial response:", response)
35+
36+
# You can set a maximum number of retries to avoid indefinite polling
37+
report_result = ReportHandler(response, client, max_retries=30)
38+
print("Final result after polling (or max retries):")
39+
print(report_result)

sample.py renamed to examples/sample.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
print('get first 5 primary zones with j: %s' % client.get_zones(offset=0, limit=5, sort="NAME", reverse=False, q={"name":"j", "zone_type":"PRIMARY"}))
8787

8888
#creating a zone with upload
89-
result = client.create_primary_zone_by_upload(account_name, 'sample.client.me.', './zone.txt')
89+
result = client.create_primary_zone_by_upload(account_name, 'sample.client.me.', '../zone.txt')
9090
print('create zone via upload: %s' % result)
9191

9292
# check the task status

examples/task_handler_example.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python
2+
"""
3+
Example script demonstrating the use of the TaskHandler utility class.
4+
5+
This script shows how to use the TaskHandler to handle asynchronous task responses
6+
from the UltraDNS API.
7+
"""
8+
9+
from ultra_rest_client import RestApiClient, TaskHandler
10+
11+
# Initialize the client
12+
client = RestApiClient('your_username', 'your_password')
13+
14+
# Example 1: Handling a response with a task_id
15+
# --------------------------------------------
16+
# Some API endpoints return a task_id indicating a background task
17+
print("Example 1: Creating a snapshot (returns a task_id)")
18+
response = client.create_snapshot('example.com')
19+
print("Initial response:", response)
20+
21+
# The TaskHandler will automatically poll the task endpoint until completion
22+
task_result = TaskHandler(response, client)
23+
print("Final result after polling:")
24+
print(task_result) # This will print the final result
25+
26+
# Example 2: Handling a response with a location
27+
# --------------------------------------------
28+
# Some API endpoints return a location that needs to be polled until completion
29+
print("\nExample 2: Creating a health check (returns a location)")
30+
response = client.create_health_check('example.com')
31+
print("Initial response:", response)
32+
33+
# The TaskHandler will automatically poll the location until completion
34+
location_result = TaskHandler(response, client, poll_interval=2) # Poll every 2 seconds
35+
print("Final result after polling:")
36+
print(location_result)
37+
38+
# Example 3: Handling a regular response (no task_id or location)
39+
# --------------------------------------------
40+
# If no task_id or location is present, the TaskHandler will return the original response
41+
print("\nExample 3: Getting zone information (returns a regular response)")
42+
response = client.get_zone('example.com')
43+
print("Initial response:", response)
44+
45+
regular_result = TaskHandler(response, client)
46+
print("Result from TaskHandler:")
47+
print(regular_result) # This will print the original response

src/ultra_rest_client/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
from .ultra_rest_client import RestApiClient
2-
from .connection import RestApiConnection
2+
from .connection import RestApiConnection
3+
from .utils.tasks import TaskHandler
4+
from .utils.reports import ReportHandler

src/ultra_rest_client/ultra_rest_client.py

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,231 @@ def export_zone(self, zone_name):
954954
self.clear_task(task_id)
955955
return result
956956

957+
# Health Checks
958+
def create_health_check(self, zone_name):
959+
"""Initiates a health check for a zone.
960+
961+
Arguments:
962+
zone_name -- The name of the zone to perform a health check on.
963+
964+
Returns:
965+
A dictionary containing the location header from the response, which includes
966+
the timestamp identifier needed to retrieve the health check results.
967+
"""
968+
return self.rest_api_connection.post(f"/v1/zones/{zone_name}/healthchecks", json.dumps({}))
969+
970+
def get_health_check(self, zone_name, timestamp):
971+
"""Retrieves the results of a previously initiated health check.
972+
973+
Arguments:
974+
zone_name -- The name of the zone that was checked.
975+
timestamp -- The timestamp identifier returned from create_health_check.
976+
977+
Returns:
978+
A dictionary containing detailed health check results, including version,
979+
state, and a list of check results with nested validation details.
980+
"""
981+
return self.rest_api_connection.get(f"/v1/zones/{zone_name}/healthchecks/{timestamp}")
982+
983+
def create_dangling_cname_check(self, zone_name):
984+
"""Initiates a dangling CNAME (DCNAME) check for a zone.
985+
986+
Arguments:
987+
zone_name -- The name of the zone to perform a dangling CNAME check on.
988+
989+
Returns:
990+
A dictionary containing the response from the API. Note that while a location
991+
header is returned, it is not used for retrieving results as only one set of
992+
DCNAME results is kept per zone.
993+
"""
994+
return self.rest_api_connection.post(f"/v1/zones/{zone_name}/healthchecks/dangling", json.dumps({}))
995+
996+
def get_dangling_cname_check(self, zone_name):
997+
"""Retrieves the results of a dangling CNAME check.
998+
999+
Arguments:
1000+
zone_name -- The name of the zone to retrieve dangling CNAME check results for.
1001+
1002+
Returns:
1003+
A dictionary containing detailed dangling CNAME check results, including version,
1004+
zone, status, resultInfo, and a list of dangling records.
1005+
"""
1006+
return self.rest_api_connection.get(f"/v1/zones/{zone_name}/healthchecks/dangling")
1007+
1008+
def create_advanced_nxdomain_report(self, start_date, end_date, zone_names, limit=100):
1009+
"""Initiates the creation of an Advanced NX Domain report.
1010+
1011+
This method sends a POST request to generate a report that identifies NX domain queries
1012+
(DNS queries for non-existent domains) for the specified zones within the given date range.
1013+
1014+
Arguments:
1015+
start_date -- Start date of the report in 'yyyy-MM-dd' format. Must not be more than 30 days prior to end_date.
1016+
end_date -- End date of the report in 'yyyy-MM-dd' format.
1017+
zone_names -- A single zone name (string) or a list of zone names to include in the report.
1018+
limit -- Optional. Number of records to return (default: 100, maximum: 100000).
1019+
1020+
Returns:
1021+
A dictionary containing the response from the API, including the requestId which can be used
1022+
with get_report_results() to retrieve the report data once processing is complete.
1023+
1024+
Example response:
1025+
{
1026+
"requestId": "HQV_NXD-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"
1027+
}
1028+
"""
1029+
# Ensure zone_names is a list
1030+
if isinstance(zone_names, str):
1031+
zone_names = [zone_names]
1032+
1033+
payload = {
1034+
"hostQueryVolume": {
1035+
"startDate": start_date,
1036+
"endDate": end_date,
1037+
"zoneNames": zone_names
1038+
},
1039+
"sortFields": {
1040+
"nxdomainCount": "DESC"
1041+
}
1042+
}
1043+
1044+
endpoint = f"/v1/reports/dns_resolution/query_volume/host?advance=true&reportType=ADVANCED_NXDOMAINS&limit={limit}"
1045+
return self.rest_api_connection.post(endpoint, json.dumps(payload))
1046+
1047+
def get_report_results(self, report_id):
1048+
"""Retrieves the results of any report using the report ID.
1049+
1050+
This method sends a GET request to fetch the results of a previously initiated report.
1051+
The report may still be processing, in which case the response will indicate this status.
1052+
1053+
Arguments:
1054+
report_id -- The report ID returned from a report creation method (e.g., create_advanced_nxdomain_report).
1055+
1056+
Returns:
1057+
A dictionary or list containing the report results if the report is complete, or an error
1058+
message indicating the report is still processing.
1059+
"""
1060+
return self.rest_api_connection.get(f"/v1/requests/{report_id}")
1061+
1062+
def create_projected_query_volume_report(self, accountName, sortFields=None):
1063+
"""Initiates the creation of a Projected Query Volume Report.
1064+
1065+
This method sends a POST request to generate a report that provides projected query volume
1066+
data for the specified account.
1067+
1068+
Arguments:
1069+
accountName -- The name of the account for which the report is being run.
1070+
sortFields -- Optional. A dictionary defining sortable columns and their sort directions.
1071+
Valid sortable columns include: 'month', 'currentDay', 'rspMtd', 'rspMtd7dAvg',
1072+
'rspMtd30dAvg', 'ttlAvg', and 'rspDaily' (each with values 'ASC' or 'DESC').
1073+
If not provided, a default sort will be applied (rspMtd: DESC).
1074+
1075+
Returns:
1076+
A dictionary containing the response from the API, including the requestId which can be used
1077+
with get_report_results() to retrieve the report data once processing is complete.
1078+
"""
1079+
payload = {
1080+
"projectedQueryVolume": {
1081+
"accountName": accountName
1082+
}
1083+
}
1084+
1085+
if sortFields:
1086+
payload["sortFields"] = sortFields
1087+
else:
1088+
payload["sortFields"] = {
1089+
"rspMtd": "DESC"
1090+
}
1091+
1092+
return self.rest_api_connection.post("/v1/reports/dns_resolution/projected_query_volume", json.dumps(payload))
1093+
1094+
def create_zone_query_volume_report(self, startDate, endDate, zoneQueryVolume=None, sortFields=None, offset=0, limit=1000):
1095+
"""Initiates the creation of a Zone Query Volume Report.
1096+
1097+
This method sends a POST request to generate a report that aggregates query volumes for multiple zones
1098+
over a specified period (up to 13 months).
1099+
1100+
Arguments:
1101+
startDate -- Start date of the report in 'YYYY-MM-DD' format.
1102+
endDate -- End date of the report in 'YYYY-MM-DD' format.
1103+
zoneQueryVolume -- Optional. A dictionary with additional fields (e.g., 'zoneName', 'accountName', 'ultra2').
1104+
sortFields -- Optional. A dictionary mapping sortable column names to sort directions ('ASC' or 'DESC').
1105+
Valid sortable columns include: 'zoneName', 'startDate', 'endDate', 'rspTotal', etc.
1106+
If not provided, default sort criteria will be applied.
1107+
offset -- Optional. Pagination offset (default: 0).
1108+
limit -- Optional. Pagination limit (default: 1000).
1109+
1110+
Returns:
1111+
A dictionary containing the response from the API, including the requestId which can be used
1112+
with get_report_results() to retrieve the report data once processing is complete.
1113+
"""
1114+
if zoneQueryVolume is None:
1115+
zoneQueryVolume = {}
1116+
1117+
zoneQueryVolume["startDate"] = startDate
1118+
zoneQueryVolume["endDate"] = endDate
1119+
1120+
payload = {
1121+
"zoneQueryVolume": zoneQueryVolume
1122+
}
1123+
1124+
if sortFields:
1125+
payload["sortFields"] = sortFields
1126+
else:
1127+
payload["sortFields"] = {
1128+
"zoneName": "ASC",
1129+
"endDate": "ASC"
1130+
}
1131+
1132+
endpoint = f"/v1/reports/dns_resolution/query_volume/zone?offset={offset}&limit={limit}"
1133+
return self.rest_api_connection.post(endpoint, json.dumps(payload))
1134+
1135+
# Zone Snapshots
1136+
def create_snapshot(self, zone_name):
1137+
"""Creates a snapshot of a zone.
1138+
1139+
This method sends a POST request to create a snapshot of the specified zone,
1140+
capturing its current state. A zone can only have one snapshot at a time.
1141+
1142+
Arguments:
1143+
zone_name -- The name of the zone to create a snapshot for.
1144+
1145+
Returns:
1146+
A dictionary containing the response from the API, including a task_id
1147+
that identifies the snapshot creation task.
1148+
"""
1149+
return self.rest_api_connection.post(f"/v1/zones/{zone_name}/snapshot", json.dumps({}))
1150+
1151+
def get_snapshot(self, zone_name):
1152+
"""Retrieves the current snapshot for a zone.
1153+
1154+
This method sends a GET request to fetch the current snapshot for the specified zone,
1155+
returning all details of the snapshot in a structured JSON object.
1156+
1157+
Arguments:
1158+
zone_name -- The name of the zone to retrieve the snapshot for.
1159+
1160+
Returns:
1161+
A dictionary containing detailed snapshot information, including the zone name
1162+
and a list of resource record sets (rrSets) with their properties.
1163+
"""
1164+
return self.rest_api_connection.get(f"/v1/zones/{zone_name}/snapshot")
1165+
1166+
def restore_snapshot(self, zone_name):
1167+
"""Restores a zone to its snapshot.
1168+
1169+
This method sends a POST request to restore the specified zone to the state
1170+
captured in its snapshot. This operation should be used with caution as it
1171+
will revert all changes made since the snapshot was created.
1172+
1173+
Arguments:
1174+
zone_name -- The name of the zone to restore from its snapshot.
1175+
1176+
Returns:
1177+
A dictionary containing the response from the API, including a task_id
1178+
that identifies the restore operation task.
1179+
"""
1180+
return self.rest_api_connection.post(f"/v1/zones/{zone_name}/restore", json.dumps({}))
1181+
9571182
def build_params(q, args):
9581183
params = args.copy()
9591184
if q:

0 commit comments

Comments
 (0)