-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathgnmi_tools.py
250 lines (220 loc) · 8.22 KB
/
gnmi_tools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
"""Wrapper module for gNMI operations providing async interface."""
import json
from typing import Dict, Any, Union, List
from datetime import datetime, timezone
import pytz
from gnmi_config import (
set_gnmi_config,
get_gnmi_data,
apply_cli_commands as apply_cli_cmds,
cleanup_connections
)
from pathlib import Path
def convert_epoch_to_aedt(epoch_ns: Union[int, float, None]) -> str:
"""Convert nanosecond epoch timestamp to AEDT time string.
Args:
epoch_ns: Epoch timestamp in nanoseconds
Returns:
str: Formatted datetime string in AEDT
"""
try:
if epoch_ns is None:
return ""
# Convert nanoseconds to seconds
epoch_sec = float(epoch_ns) / 1e9
# Convert to datetime
utc_dt = datetime.fromtimestamp(epoch_sec, timezone.utc)
# Convert to AEDT
aedt = pytz.timezone('Australia/Sydney')
aedt_dt = utc_dt.astimezone(aedt)
# Format the datetime
return aedt_dt.strftime('%Y-%m-%d %H:%M:%S %Z')
except (ValueError, TypeError, OverflowError):
return str(epoch_ns) # Return original value if conversion fails
def load_device_info():
"""Load device information from hosts.json."""
try:
with open('hosts.json', 'r') as f:
return json.load(f)
except Exception as e:
print(f"Error loading hosts.json: {e}")
return {"devices": {}}
def resolve_target(target_spec: str) -> Dict[str, str]:
"""Resolve a single target specification to connection details.
Args:
target_spec: Hostname or target address
Returns:
Dict containing target, username, and password
"""
devices = load_device_info().get("devices", {})
# Check if it's a hostname in our devices
if target_spec in devices:
device = devices[target_spec]
return {
"target": device["target"],
"username": device["username"],
"password": device["password"]
}
else:
# Assume it's a direct target address
first_device = next(iter(devices.values()))
return {
"target": target_spec,
"username": first_device["username"],
"password": first_device["password"]
}
async def gnmi_configure(arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute gNMI configuration.
Args:
arguments: Dictionary containing:
- target: Target device or list of devices (hostname or address)
- path: gNMI path
- value: Value to set
- username: Optional device username (if not using hostname)
- password: Optional device password (if not using hostname)
Returns:
Dict containing operation result
"""
try:
target_spec = arguments.get("target")
path = arguments.get("path")
value = arguments.get("value")
# For CLI commands, ensure proper path format
if path == "cli:" and not value.startswith("cli:"):
path = "cli:"
# Handle multiple targets concurrently
if isinstance(target_spec, list):
import asyncio
tasks = []
for t in target_spec:
conn_info = resolve_target(t)
tasks.append(
set_gnmi_config(
target=conn_info["target"],
path=path,
value=value,
username=conn_info["username"],
password=conn_info["password"]
)
)
responses = await asyncio.gather(*tasks)
return {
"status": "success",
"responses": responses
}
else:
# Single target
conn_info = resolve_target(target_spec)
response = await set_gnmi_config(
target=conn_info["target"],
path=path,
value=value,
username=conn_info["username"],
password=conn_info["password"]
)
return response
except Exception as e:
return {"error": str(e)}
finally:
# Cleanup connections if there was an error
if 'error' in locals():
cleanup_connections()
async def gnmi_get(arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Retrieve data from an Arista device using gNMI.
Args:
arguments: Dictionary containing:
- target: Target device or list of devices (hostname or address)
- path: gNMI path to query
- username: Optional device username (if not using hostname)
- password: Optional device password (if not using hostname)
Returns:
Dict containing retrieved data
"""
try:
target_spec = arguments.get("target")
path = arguments.get("path")
# Handle multiple targets concurrently
if isinstance(target_spec, list):
import asyncio
tasks = []
for t in target_spec:
conn_info = resolve_target(t)
tasks.append(
get_gnmi_data(
target=conn_info["target"],
path=path,
username=conn_info["username"],
password=conn_info["password"]
)
)
responses = await asyncio.gather(*tasks)
return {
"status": "success",
"responses": responses
}
else:
# Single target
conn_info = resolve_target(target_spec)
response = await get_gnmi_data(
target=conn_info["target"],
path=path,
username=conn_info["username"],
password=conn_info["password"]
)
return response
except Exception as e:
return {"error": str(e)}
finally:
# Cleanup connections if there was an error
if 'error' in locals():
cleanup_connections()
async def apply_cli_commands(arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Execute multiple CLI commands via gNMI.
Args:
arguments: Dictionary containing:
- target: Target device or list of devices (hostname or address)
- commands: List of CLI commands to execute
- username: Optional device username (if not using hostname)
- password: Optional device password (if not using hostname)
Returns:
Dict containing execution results
"""
try:
target_spec = arguments.get("target")
commands = arguments.get("commands")
if not isinstance(commands, list):
raise ValueError("Commands must be provided as a list")
# Handle target resolution
if isinstance(target_spec, list):
resolved_targets = []
usernames = set()
passwords = set()
for t in target_spec:
conn_info = resolve_target(t)
resolved_targets.append(conn_info["target"])
usernames.add(conn_info["username"])
passwords.add(conn_info["password"])
# Ensure consistent credentials
if len(usernames) > 1 or len(passwords) > 1:
raise ValueError("Inconsistent credentials across specified devices")
target = resolved_targets
username = usernames.pop()
password = passwords.pop()
else:
conn_info = resolve_target(target_spec)
target = conn_info["target"]
username = conn_info["username"]
password = conn_info["password"]
response = await apply_cli_cmds({
"target": target,
"commands": commands,
"username": username,
"password": password
})
return response
except Exception as e:
return {"error": str(e)}
finally:
# Cleanup connections if there was an error
if 'error' in locals():
cleanup_connections()