fabric_reports_client 0.1.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.
- fabric_reports_client-0.1.0/LICENSE +21 -0
- fabric_reports_client-0.1.0/PKG-INFO +10 -0
- fabric_reports_client-0.1.0/README.md +0 -0
- fabric_reports_client-0.1.0/fabric_reports_client/__init__.py +0 -0
- fabric_reports_client-0.1.0/fabric_reports_client/reports_api.py +645 -0
- fabric_reports_client-0.1.0/pyproject.toml +18 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FABRIC Testbed
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ReportsApi:
|
|
7
|
+
def __init__(self, base_url: str, token_file: str):
|
|
8
|
+
self.base_url = base_url.rstrip("/")
|
|
9
|
+
self.token = self._load_token(token_file)
|
|
10
|
+
self.headers = {
|
|
11
|
+
"Authorization": f"Bearer {self.token}",
|
|
12
|
+
"Accept": "application/json"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def _load_token(self, token_file: str) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Load bearer token from a JSON file with structure: { "id_token": "<token>" }
|
|
18
|
+
"""
|
|
19
|
+
if not os.path.exists(token_file):
|
|
20
|
+
raise FileNotFoundError(f"Token file '{token_file}' not found")
|
|
21
|
+
|
|
22
|
+
with open(token_file, 'r') as f:
|
|
23
|
+
data = json.load(f)
|
|
24
|
+
|
|
25
|
+
token = data.get("id_token")
|
|
26
|
+
if not token:
|
|
27
|
+
raise ValueError("Missing 'id_token' field in token JSON file")
|
|
28
|
+
return token
|
|
29
|
+
|
|
30
|
+
def query_sites(self):
|
|
31
|
+
"""
|
|
32
|
+
Query the /slices endpoint with optional filters.
|
|
33
|
+
"""
|
|
34
|
+
url = f"{self.base_url}/sites"
|
|
35
|
+
|
|
36
|
+
response = requests.get(url, headers=self.headers)
|
|
37
|
+
|
|
38
|
+
if response.status_code == 200:
|
|
39
|
+
return response.json()
|
|
40
|
+
else:
|
|
41
|
+
raise Exception(f"Failed to fetch slices: {response.status_code} - {response.text}")
|
|
42
|
+
|
|
43
|
+
def query_slices(self, start_time: str = None, end_time: str = None, user_id: list[str] = None,
|
|
44
|
+
user_email: list[str] = None, project_id: list[str] = None, slice_id: list[str] = None,
|
|
45
|
+
slice_state: list[str] = None, sliver_id: list[str] = None, sliver_type: list[str] = None,
|
|
46
|
+
sliver_state: list[str] = None, component_type: list[str] = None,
|
|
47
|
+
component_model: list[str] = None, bdf: list[str] = None, vlan: list[str] = None,
|
|
48
|
+
ip_subnet: list[str] = None, site: list[str] = None, host: list[str] = None,
|
|
49
|
+
exclude_user_id: list[str] = None, exclude_user_email: list[str] = None,
|
|
50
|
+
exclude_project_id: list[str] = None, exclude_site: list[str] = None, facility: list[str] = None,
|
|
51
|
+
exclude_host: list[str] = None, page=0, per_page=100, fetch_all=True):
|
|
52
|
+
"""
|
|
53
|
+
Fetch slices with optional filters. Supports fetching all pages or just one.
|
|
54
|
+
|
|
55
|
+
:param start_time: Filter by start time (inclusive)
|
|
56
|
+
:type start_time: str
|
|
57
|
+
:param end_time: Filter by end time (inclusive)
|
|
58
|
+
:type end_time: str
|
|
59
|
+
:param user_id: Filter by user uuid
|
|
60
|
+
:type user_id: List[str]
|
|
61
|
+
:param user_email: Filter by user email
|
|
62
|
+
:type user_email: List[str]
|
|
63
|
+
:param project_id: Filter by project uuid
|
|
64
|
+
:type project_id: List[str]
|
|
65
|
+
:param slice_id: Filter by slice uuid
|
|
66
|
+
:type slice_id: List[str]
|
|
67
|
+
:param slice_state: Filter by slice state; allowed values Nascent, Configuring, StableError, StableOK, Closing, Dead, Modifying, ModifyOK, ModifyError, AllocatedError, AllocatedOK
|
|
68
|
+
:type slice_state: List[str]
|
|
69
|
+
:param sliver_id: Filter by sliver uuid
|
|
70
|
+
:type sliver_id: List[str]
|
|
71
|
+
:param sliver_type: Filter by sliver type; allowed values VM, Switch, Facility, L2STS, L2PTP, L2Bridge, FABNetv4, FABNetv6, PortMirror, L3VPN, FABNetv4Ext, FABNetv6Ext
|
|
72
|
+
:type sliver_type: List[str]
|
|
73
|
+
:param sliver_state: Filter by sliver state; allowed values Nascent, Ticketed, Active, ActiveTicketed, Closed, CloseWait, Failed, Unknown, CloseFail
|
|
74
|
+
:type sliver_state: List[str]
|
|
75
|
+
:param component_type: Filter by component type, allowed values GPU, SmartNIC, SharedNIC, FPGA, NVME, Storage
|
|
76
|
+
:type component_type: List[str]
|
|
77
|
+
:param component_model: Filter by component model
|
|
78
|
+
:type component_model: List[str]
|
|
79
|
+
:param bdf: Filter by specified BDF (Bus:Device.Function) of interfaces/components
|
|
80
|
+
:type bdf: List[str]
|
|
81
|
+
:param vlan: Filter by VLAN associated with their sliver interfaces.
|
|
82
|
+
:type vlan: List[str]
|
|
83
|
+
:param ip_subnet: Filter by specified IP subnet
|
|
84
|
+
:type ip_subnet: List[str]
|
|
85
|
+
:param site: Filter by site
|
|
86
|
+
:type site: List[str]
|
|
87
|
+
:param host: Filter by host
|
|
88
|
+
:type host: List[str]
|
|
89
|
+
:param host: Filter by host
|
|
90
|
+
:type host: List[str]
|
|
91
|
+
:param exclude_user_id: Exclude Users by IDs
|
|
92
|
+
:type exclude_user_id: List[str]
|
|
93
|
+
:param exclude_user_email: Exclude Users by emails
|
|
94
|
+
:type exclude_user_email: List[str]
|
|
95
|
+
:param exclude_project_id: Exclude projects
|
|
96
|
+
:type exclude_project_id: List[str]
|
|
97
|
+
:param exclude_site: Exclude sites
|
|
98
|
+
:type exclude_site: List[str]
|
|
99
|
+
:param exclude_host: Exclude hosts
|
|
100
|
+
:type exclude_host: List[str]
|
|
101
|
+
:param page: Page number for pagination. Default is 1.
|
|
102
|
+
:type page: int
|
|
103
|
+
:param per_page: Number of records per page. Default is 10.
|
|
104
|
+
:type per_page: int
|
|
105
|
+
:param fetch_all: If True, paginates until all results are fetched.
|
|
106
|
+
:return: Dict with 'total' and 'data' keys.
|
|
107
|
+
"""
|
|
108
|
+
all_slices = []
|
|
109
|
+
total = 0
|
|
110
|
+
url = f"{self.base_url}/slices"
|
|
111
|
+
|
|
112
|
+
base_params = {
|
|
113
|
+
"start_time": start_time,
|
|
114
|
+
"end_time": end_time,
|
|
115
|
+
"user_id": user_id,
|
|
116
|
+
"user_email": user_email,
|
|
117
|
+
"project_id": project_id,
|
|
118
|
+
"slice_id": slice_id,
|
|
119
|
+
"slice_state": slice_state,
|
|
120
|
+
"sliver_id": sliver_id,
|
|
121
|
+
"sliver_type": sliver_type,
|
|
122
|
+
"sliver_state": sliver_state,
|
|
123
|
+
"component_type": component_type,
|
|
124
|
+
"component_model": component_model,
|
|
125
|
+
"bdf": bdf,
|
|
126
|
+
"vlan": vlan,
|
|
127
|
+
"ip_subnet": ip_subnet,
|
|
128
|
+
"site": site,
|
|
129
|
+
"host": host,
|
|
130
|
+
"facility": facility,
|
|
131
|
+
"exclude_user_id": exclude_user_id,
|
|
132
|
+
"exclude_user_email": exclude_user_email,
|
|
133
|
+
"exclude_project_id": exclude_project_id,
|
|
134
|
+
"exclude_site": exclude_site,
|
|
135
|
+
"exclude_host": exclude_host,
|
|
136
|
+
"per_page": per_page # page will be added per iteration
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Remove keys with None values
|
|
140
|
+
filtered_params = {k: v for k, v in base_params.items() if v is not None}
|
|
141
|
+
|
|
142
|
+
while True:
|
|
143
|
+
filtered_params["page"] = page
|
|
144
|
+
response = requests.get(url, headers=self.headers, params=filtered_params)
|
|
145
|
+
|
|
146
|
+
if response.status_code == 200:
|
|
147
|
+
response = response.json()
|
|
148
|
+
else:
|
|
149
|
+
raise Exception(f"Failed to fetch slices: {response.status_code} - {response.text}")
|
|
150
|
+
|
|
151
|
+
if page == 0:
|
|
152
|
+
total = response.get("total")
|
|
153
|
+
|
|
154
|
+
data = response.get("data", [])
|
|
155
|
+
all_slices.extend(data)
|
|
156
|
+
|
|
157
|
+
if not fetch_all or not data or len(all_slices) >= total:
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
page += 1
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
"total": total,
|
|
164
|
+
"data": all_slices
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def query_slivers(self, start_time: str = None, end_time: str = None, user_id: list[str] = None,
|
|
168
|
+
user_email: list[str] = None, project_id: list[str] = None, slice_id: list[str] = None,
|
|
169
|
+
slice_state: list[str] = None, sliver_id: list[str] = None, sliver_type: list[str] = None,
|
|
170
|
+
sliver_state: list[str] = None, component_type: list[str] = None,
|
|
171
|
+
component_model: list[str] = None, bdf: list[str] = None, vlan: list[str] = None,
|
|
172
|
+
ip_subnet: list[str] = None, site: list[str] = None, host: list[str] = None,
|
|
173
|
+
exclude_user_id: list[str] = None, exclude_user_email: list[str] = None,
|
|
174
|
+
exclude_project_id: list[str] = None, exclude_site: list[str] = None, facility: list[str] = None,
|
|
175
|
+
exclude_host: list[str] = None, page=0, per_page=100, fetch_all=True):
|
|
176
|
+
"""
|
|
177
|
+
Fetch slivers with optional filters. Supports fetching all pages or just one.
|
|
178
|
+
|
|
179
|
+
:param start_time: Filter by start time (inclusive)
|
|
180
|
+
:type start_time: str
|
|
181
|
+
:param end_time: Filter by end time (inclusive)
|
|
182
|
+
:type end_time: str
|
|
183
|
+
:param user_id: Filter by user uuid
|
|
184
|
+
:type user_id: List[str]
|
|
185
|
+
:param user_email: Filter by user email
|
|
186
|
+
:type user_email: List[str]
|
|
187
|
+
:param project_id: Filter by project uuid
|
|
188
|
+
:type project_id: List[str]
|
|
189
|
+
:param slice_id: Filter by slice uuid
|
|
190
|
+
:type slice_id: List[str]
|
|
191
|
+
:param slice_state: Filter by slice state; allowed values Nascent, Configuring, StableError, StableOK, Closing, Dead, Modifying, ModifyOK, ModifyError, AllocatedError, AllocatedOK
|
|
192
|
+
:type slice_state: List[str]
|
|
193
|
+
:param sliver_id: Filter by sliver uuid
|
|
194
|
+
:type sliver_id: List[str]
|
|
195
|
+
:param sliver_type: Filter by sliver type; allowed values VM, Switch, Facility, L2STS, L2PTP, L2Bridge, FABNetv4, FABNetv6, PortMirror, L3VPN, FABNetv4Ext, FABNetv6Ext
|
|
196
|
+
:type sliver_type: List[str]
|
|
197
|
+
:param sliver_state: Filter by sliver state; allowed values Nascent, Ticketed, Active, ActiveTicketed, Closed, CloseWait, Failed, Unknown, CloseFail
|
|
198
|
+
:type sliver_state: List[str]
|
|
199
|
+
:param component_type: Filter by component type, allowed values GPU, SmartNIC, SharedNIC, FPGA, NVME, Storage
|
|
200
|
+
:type component_type: List[str]
|
|
201
|
+
:param component_model: Filter by component model
|
|
202
|
+
:type component_model: List[str]
|
|
203
|
+
:param bdf: Filter by specified BDF (Bus:Device.Function) of interfaces/components
|
|
204
|
+
:type bdf: List[str]
|
|
205
|
+
:param vlan: Filter by VLAN associated with their sliver interfaces.
|
|
206
|
+
:type vlan: List[str]
|
|
207
|
+
:param ip_subnet: Filter by specified IP subnet
|
|
208
|
+
:type ip_subnet: List[str]
|
|
209
|
+
:param site: Filter by site
|
|
210
|
+
:type site: List[str]
|
|
211
|
+
:param host: Filter by host
|
|
212
|
+
:type host: List[str]
|
|
213
|
+
:param host: Filter by host
|
|
214
|
+
:type host: List[str]
|
|
215
|
+
:param exclude_user_id: Exclude Users by IDs
|
|
216
|
+
:type exclude_user_id: List[str]
|
|
217
|
+
:param exclude_user_email: Exclude Users by emails
|
|
218
|
+
:type exclude_user_email: List[str]
|
|
219
|
+
:param exclude_project_id: Exclude projects
|
|
220
|
+
:type exclude_project_id: List[str]
|
|
221
|
+
:param exclude_site: Exclude sites
|
|
222
|
+
:type exclude_site: List[str]
|
|
223
|
+
:param exclude_host: Exclude hosts
|
|
224
|
+
:type exclude_host: List[str]
|
|
225
|
+
:param page: Page number for pagination. Default is 1.
|
|
226
|
+
:type page: int
|
|
227
|
+
:param per_page: Number of records per page. Default is 10.
|
|
228
|
+
:type per_page: int
|
|
229
|
+
|
|
230
|
+
:param fetch_all: If True, paginates until all results are fetched.
|
|
231
|
+
:return: Dict with 'total' and 'data' keys.
|
|
232
|
+
"""
|
|
233
|
+
all_slivers = []
|
|
234
|
+
total = 0
|
|
235
|
+
url = f"{self.base_url}/slivers"
|
|
236
|
+
|
|
237
|
+
base_params = {
|
|
238
|
+
"start_time": start_time,
|
|
239
|
+
"end_time": end_time,
|
|
240
|
+
"user_id": user_id,
|
|
241
|
+
"user_email": user_email,
|
|
242
|
+
"project_id": project_id,
|
|
243
|
+
"slice_id": slice_id,
|
|
244
|
+
"slice_state": slice_state,
|
|
245
|
+
"sliver_id": sliver_id,
|
|
246
|
+
"sliver_type": sliver_type,
|
|
247
|
+
"sliver_state": sliver_state,
|
|
248
|
+
"component_type": component_type,
|
|
249
|
+
"component_model": component_model,
|
|
250
|
+
"bdf": bdf,
|
|
251
|
+
"vlan": vlan,
|
|
252
|
+
"ip_subnet": ip_subnet,
|
|
253
|
+
"site": site,
|
|
254
|
+
"host": host,
|
|
255
|
+
"facility": facility,
|
|
256
|
+
"exclude_user_id": exclude_user_id,
|
|
257
|
+
"exclude_user_email": exclude_user_email,
|
|
258
|
+
"exclude_project_id": exclude_project_id,
|
|
259
|
+
"exclude_site": exclude_site,
|
|
260
|
+
"exclude_host": exclude_host,
|
|
261
|
+
"per_page": per_page # page will be added per iteration
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
# Remove keys with None values
|
|
265
|
+
filtered_params = {k: v for k, v in base_params.items() if v is not None}
|
|
266
|
+
|
|
267
|
+
while True:
|
|
268
|
+
filtered_params["page"] = page
|
|
269
|
+
response = requests.get(url, headers=self.headers, params=filtered_params)
|
|
270
|
+
|
|
271
|
+
if response.status_code == 200:
|
|
272
|
+
response = response.json()
|
|
273
|
+
else:
|
|
274
|
+
raise Exception(f"Failed to fetch slices: {response.status_code} - {response.text}")
|
|
275
|
+
|
|
276
|
+
if page == 0:
|
|
277
|
+
total = response.get("total")
|
|
278
|
+
|
|
279
|
+
data = response.get("data", [])
|
|
280
|
+
all_slivers.extend(data)
|
|
281
|
+
|
|
282
|
+
if not fetch_all or not data or len(all_slivers) >= total:
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
page += 1
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
"total": total,
|
|
289
|
+
"data": all_slivers
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
def query_users(self, start_time: str = None, end_time: str = None, user_id: list[str] = None,
|
|
293
|
+
user_email: list[str] = None, project_id: list[str] = None, slice_id: list[str] = None,
|
|
294
|
+
slice_state: list[str] = None, sliver_id: list[str] = None, sliver_type: list[str] = None,
|
|
295
|
+
sliver_state: list[str] = None, component_type: list[str] = None,
|
|
296
|
+
component_model: list[str] = None, bdf: list[str] = None, vlan: list[str] = None,
|
|
297
|
+
ip_subnet: list[str] = None, site: list[str] = None, host: list[str] = None,
|
|
298
|
+
exclude_user_id: list[str] = None, exclude_user_email: list[str] = None,
|
|
299
|
+
exclude_project_id: list[str] = None, exclude_site: list[str] = None, facility: list[str] = None,
|
|
300
|
+
exclude_host: list[str] = None, page=0, per_page=100, fetch_all=True):
|
|
301
|
+
"""
|
|
302
|
+
Fetch users with optional filters. Supports fetching all pages or just one.
|
|
303
|
+
|
|
304
|
+
:param start_time: Filter by start time (inclusive)
|
|
305
|
+
:type start_time: str
|
|
306
|
+
:param end_time: Filter by end time (inclusive)
|
|
307
|
+
:type end_time: str
|
|
308
|
+
:param user_id: Filter by user uuid
|
|
309
|
+
:type user_id: List[str]
|
|
310
|
+
:param user_email: Filter by user email
|
|
311
|
+
:type user_email: List[str]
|
|
312
|
+
:param project_id: Filter by project uuid
|
|
313
|
+
:type project_id: List[str]
|
|
314
|
+
:param slice_id: Filter by slice uuid
|
|
315
|
+
:type slice_id: List[str]
|
|
316
|
+
:param slice_state: Filter by slice state; allowed values Nascent, Configuring, StableError, StableOK, Closing, Dead, Modifying, ModifyOK, ModifyError, AllocatedError, AllocatedOK
|
|
317
|
+
:type slice_state: List[str]
|
|
318
|
+
:param sliver_id: Filter by sliver uuid
|
|
319
|
+
:type sliver_id: List[str]
|
|
320
|
+
:param sliver_type: Filter by sliver type; allowed values VM, Switch, Facility, L2STS, L2PTP, L2Bridge, FABNetv4, FABNetv6, PortMirror, L3VPN, FABNetv4Ext, FABNetv6Ext
|
|
321
|
+
:type sliver_type: List[str]
|
|
322
|
+
:param sliver_state: Filter by sliver state; allowed values Nascent, Ticketed, Active, ActiveTicketed, Closed, CloseWait, Failed, Unknown, CloseFail
|
|
323
|
+
:type sliver_state: List[str]
|
|
324
|
+
:param component_type: Filter by component type, allowed values GPU, SmartNIC, SharedNIC, FPGA, NVME, Storage
|
|
325
|
+
:type component_type: List[str]
|
|
326
|
+
:param component_model: Filter by component model
|
|
327
|
+
:type component_model: List[str]
|
|
328
|
+
:param bdf: Filter by specified BDF (Bus:Device.Function) of interfaces/components
|
|
329
|
+
:type bdf: List[str]
|
|
330
|
+
:param vlan: Filter by VLAN associated with their sliver interfaces.
|
|
331
|
+
:type vlan: List[str]
|
|
332
|
+
:param ip_subnet: Filter by specified IP subnet
|
|
333
|
+
:type ip_subnet: List[str]
|
|
334
|
+
:param site: Filter by site
|
|
335
|
+
:type site: List[str]
|
|
336
|
+
:param host: Filter by host
|
|
337
|
+
:type host: List[str]
|
|
338
|
+
:param host: Filter by host
|
|
339
|
+
:type host: List[str]
|
|
340
|
+
:param exclude_user_id: Exclude Users by IDs
|
|
341
|
+
:type exclude_user_id: List[str]
|
|
342
|
+
:param exclude_user_email: Exclude Users by emails
|
|
343
|
+
:type exclude_user_email: List[str]
|
|
344
|
+
:param exclude_project_id: Exclude projects
|
|
345
|
+
:type exclude_project_id: List[str]
|
|
346
|
+
:param exclude_site: Exclude sites
|
|
347
|
+
:type exclude_site: List[str]
|
|
348
|
+
:param exclude_host: Exclude hosts
|
|
349
|
+
:type exclude_host: List[str]
|
|
350
|
+
:param page: Page number for pagination. Default is 1.
|
|
351
|
+
:type page: int
|
|
352
|
+
:param per_page: Number of records per page. Default is 10.
|
|
353
|
+
:type per_page: int
|
|
354
|
+
|
|
355
|
+
:param fetch_all: If True, paginates until all results are fetched.
|
|
356
|
+
:return: Dict with 'total' and 'data' keys.
|
|
357
|
+
"""
|
|
358
|
+
all_users = []
|
|
359
|
+
total = 0
|
|
360
|
+
url = f"{self.base_url}/users"
|
|
361
|
+
|
|
362
|
+
base_params = {
|
|
363
|
+
"start_time": start_time,
|
|
364
|
+
"end_time": end_time,
|
|
365
|
+
"user_id": user_id,
|
|
366
|
+
"user_email": user_email,
|
|
367
|
+
"project_id": project_id,
|
|
368
|
+
"slice_id": slice_id,
|
|
369
|
+
"slice_state": slice_state,
|
|
370
|
+
"sliver_id": sliver_id,
|
|
371
|
+
"sliver_type": sliver_type,
|
|
372
|
+
"sliver_state": sliver_state,
|
|
373
|
+
"component_type": component_type,
|
|
374
|
+
"component_model": component_model,
|
|
375
|
+
"bdf": bdf,
|
|
376
|
+
"vlan": vlan,
|
|
377
|
+
"ip_subnet": ip_subnet,
|
|
378
|
+
"site": site,
|
|
379
|
+
"host": host,
|
|
380
|
+
"facility": facility,
|
|
381
|
+
"exclude_user_id": exclude_user_id,
|
|
382
|
+
"exclude_user_email": exclude_user_email,
|
|
383
|
+
"exclude_project_id": exclude_project_id,
|
|
384
|
+
"exclude_site": exclude_site,
|
|
385
|
+
"exclude_host": exclude_host,
|
|
386
|
+
"per_page": per_page # page will be added per iteration
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
# Remove keys with None values
|
|
390
|
+
filtered_params = {k: v for k, v in base_params.items() if v is not None}
|
|
391
|
+
|
|
392
|
+
while True:
|
|
393
|
+
filtered_params["page"] = page
|
|
394
|
+
response = requests.get(url, headers=self.headers, params=filtered_params)
|
|
395
|
+
|
|
396
|
+
if response.status_code == 200:
|
|
397
|
+
response = response.json()
|
|
398
|
+
else:
|
|
399
|
+
raise Exception(f"Failed to fetch slices: {response.status_code} - {response.text}")
|
|
400
|
+
|
|
401
|
+
if page == 0:
|
|
402
|
+
total = response.get("total")
|
|
403
|
+
|
|
404
|
+
data = response.get("data", [])
|
|
405
|
+
all_users.extend(data)
|
|
406
|
+
|
|
407
|
+
if not fetch_all or not data or len(all_users) >= total:
|
|
408
|
+
break
|
|
409
|
+
|
|
410
|
+
page += 1
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
"total": total,
|
|
414
|
+
"data": all_users
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
def query_projects(self, start_time: str = None, end_time: str = None, user_id: list[str] = None,
|
|
418
|
+
user_email: list[str] = None, project_id: list[str] = None, slice_id: list[str] = None,
|
|
419
|
+
slice_state: list[str] = None, sliver_id: list[str] = None, sliver_type: list[str] = None,
|
|
420
|
+
sliver_state: list[str] = None, component_type: list[str] = None,
|
|
421
|
+
component_model: list[str] = None, bdf: list[str] = None, vlan: list[str] = None,
|
|
422
|
+
ip_subnet: list[str] = None, site: list[str] = None, host: list[str] = None,
|
|
423
|
+
exclude_user_id: list[str] = None, exclude_user_email: list[str] = None,
|
|
424
|
+
exclude_project_id: list[str] = None, exclude_site: list[str] = None, facility: list[str] = None,
|
|
425
|
+
exclude_host: list[str] = None, page=0, per_page=100, fetch_all=True):
|
|
426
|
+
"""
|
|
427
|
+
Fetch projects with optional filters. Supports fetching all pages or just one.
|
|
428
|
+
|
|
429
|
+
:param start_time: Filter by start time (inclusive)
|
|
430
|
+
:type start_time: str
|
|
431
|
+
:param end_time: Filter by end time (inclusive)
|
|
432
|
+
:type end_time: str
|
|
433
|
+
:param user_id: Filter by user uuid
|
|
434
|
+
:type user_id: List[str]
|
|
435
|
+
:param user_email: Filter by user email
|
|
436
|
+
:type user_email: List[str]
|
|
437
|
+
:param project_id: Filter by project uuid
|
|
438
|
+
:type project_id: List[str]
|
|
439
|
+
:param slice_id: Filter by slice uuid
|
|
440
|
+
:type slice_id: List[str]
|
|
441
|
+
:param slice_state: Filter by slice state; allowed values Nascent, Configuring, StableError, StableOK, Closing, Dead, Modifying, ModifyOK, ModifyError, AllocatedError, AllocatedOK
|
|
442
|
+
:type slice_state: List[str]
|
|
443
|
+
:param sliver_id: Filter by sliver uuid
|
|
444
|
+
:type sliver_id: List[str]
|
|
445
|
+
:param sliver_type: Filter by sliver type; allowed values VM, Switch, Facility, L2STS, L2PTP, L2Bridge, FABNetv4, FABNetv6, PortMirror, L3VPN, FABNetv4Ext, FABNetv6Ext
|
|
446
|
+
:type sliver_type: List[str]
|
|
447
|
+
:param sliver_state: Filter by sliver state; allowed values Nascent, Ticketed, Active, ActiveTicketed, Closed, CloseWait, Failed, Unknown, CloseFail
|
|
448
|
+
:type sliver_state: List[str]
|
|
449
|
+
:param component_type: Filter by component type, allowed values GPU, SmartNIC, SharedNIC, FPGA, NVME, Storage
|
|
450
|
+
:type component_type: List[str]
|
|
451
|
+
:param component_model: Filter by component model
|
|
452
|
+
:type component_model: List[str]
|
|
453
|
+
:param bdf: Filter by specified BDF (Bus:Device.Function) of interfaces/components
|
|
454
|
+
:type bdf: List[str]
|
|
455
|
+
:param vlan: Filter by VLAN associated with their sliver interfaces.
|
|
456
|
+
:type vlan: List[str]
|
|
457
|
+
:param ip_subnet: Filter by specified IP subnet
|
|
458
|
+
:type ip_subnet: List[str]
|
|
459
|
+
:param site: Filter by site
|
|
460
|
+
:type site: List[str]
|
|
461
|
+
:param host: Filter by host
|
|
462
|
+
:type host: List[str]
|
|
463
|
+
:param host: Filter by host
|
|
464
|
+
:type host: List[str]
|
|
465
|
+
:param exclude_user_id: Exclude Users by IDs
|
|
466
|
+
:type exclude_user_id: List[str]
|
|
467
|
+
:param exclude_user_email: Exclude Users by emails
|
|
468
|
+
:type exclude_user_email: List[str]
|
|
469
|
+
:param exclude_project_id: Exclude projects
|
|
470
|
+
:type exclude_project_id: List[str]
|
|
471
|
+
:param exclude_site: Exclude sites
|
|
472
|
+
:type exclude_site: List[str]
|
|
473
|
+
:param exclude_host: Exclude hosts
|
|
474
|
+
:type exclude_host: List[str]
|
|
475
|
+
:param page: Page number for pagination. Default is 1.
|
|
476
|
+
:type page: int
|
|
477
|
+
:param per_page: Number of records per page. Default is 10.
|
|
478
|
+
:type per_page: int
|
|
479
|
+
|
|
480
|
+
:param fetch_all: If True, paginates until all results are fetched.
|
|
481
|
+
:return: Dict with 'total' and 'data' keys.
|
|
482
|
+
"""
|
|
483
|
+
all_projects = []
|
|
484
|
+
total = 0
|
|
485
|
+
url = f"{self.base_url}/projects"
|
|
486
|
+
|
|
487
|
+
base_params = {
|
|
488
|
+
"start_time": start_time,
|
|
489
|
+
"end_time": end_time,
|
|
490
|
+
"user_id": user_id,
|
|
491
|
+
"user_email": user_email,
|
|
492
|
+
"project_id": project_id,
|
|
493
|
+
"slice_id": slice_id,
|
|
494
|
+
"slice_state": slice_state,
|
|
495
|
+
"sliver_id": sliver_id,
|
|
496
|
+
"sliver_type": sliver_type,
|
|
497
|
+
"sliver_state": sliver_state,
|
|
498
|
+
"component_type": component_type,
|
|
499
|
+
"component_model": component_model,
|
|
500
|
+
"bdf": bdf,
|
|
501
|
+
"vlan": vlan,
|
|
502
|
+
"ip_subnet": ip_subnet,
|
|
503
|
+
"site": site,
|
|
504
|
+
"host": host,
|
|
505
|
+
"facility": facility,
|
|
506
|
+
"exclude_user_id": exclude_user_id,
|
|
507
|
+
"exclude_user_email": exclude_user_email,
|
|
508
|
+
"exclude_project_id": exclude_project_id,
|
|
509
|
+
"exclude_site": exclude_site,
|
|
510
|
+
"exclude_host": exclude_host,
|
|
511
|
+
"per_page": per_page # page will be added per iteration
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
# Remove keys with None values
|
|
515
|
+
filtered_params = {k: v for k, v in base_params.items() if v is not None}
|
|
516
|
+
|
|
517
|
+
while True:
|
|
518
|
+
filtered_params["page"] = page
|
|
519
|
+
response = requests.get(url, headers=self.headers, params=filtered_params)
|
|
520
|
+
|
|
521
|
+
if response.status_code == 200:
|
|
522
|
+
response = response.json()
|
|
523
|
+
else:
|
|
524
|
+
raise Exception(f"Failed to fetch slices: {response.status_code} - {response.text}")
|
|
525
|
+
|
|
526
|
+
if page == 0:
|
|
527
|
+
total = response.get("total")
|
|
528
|
+
|
|
529
|
+
data = response.get("data", [])
|
|
530
|
+
all_projects.extend(data)
|
|
531
|
+
|
|
532
|
+
if not fetch_all or not data or len(all_projects) >= total:
|
|
533
|
+
break
|
|
534
|
+
|
|
535
|
+
page += 1
|
|
536
|
+
|
|
537
|
+
return {
|
|
538
|
+
"total": total,
|
|
539
|
+
"data": all_projects
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
def post_sliver(self, slice_id: str, sliver_id: str, sliver_payload: dict):
|
|
543
|
+
"""
|
|
544
|
+
Create or update a sliver for a given slice ID and sliver ID.
|
|
545
|
+
|
|
546
|
+
:param slice_id: UUID of the slice
|
|
547
|
+
:type slice_id: str
|
|
548
|
+
:param sliver_id: UUID of the sliver
|
|
549
|
+
:type sliver_id: str
|
|
550
|
+
:param sliver_payload: Dictionary containing the sliver specification
|
|
551
|
+
:type sliver_payload: dict
|
|
552
|
+
:return: Server response as a dictionary
|
|
553
|
+
:rtype: dict
|
|
554
|
+
|
|
555
|
+
Example sliver dictionary:
|
|
556
|
+
sliver_payload = {
|
|
557
|
+
"project_id": "d78f130a-29b2-4f78-b9a9-87828d6cb2e2",
|
|
558
|
+
"project_name": "Edge AI Project",
|
|
559
|
+
"slice_id": "a3f41e9a-7e2b-4df7-baf7-12f48a3c8e6f",
|
|
560
|
+
"slice_name": "edge-ai-slice",
|
|
561
|
+
"user_id": "u98f124b-2332-483f-92b7-3bfbfb06b6e0",
|
|
562
|
+
"user_email": "alice@example.com",
|
|
563
|
+
"host": "host1.edge.fabric",
|
|
564
|
+
"site": "RENC",
|
|
565
|
+
"sliver_id": "c9d3f9b2-cc40-44b0-ae3a-f3a9f7a87771",
|
|
566
|
+
"node_id": "n1",
|
|
567
|
+
"state": "Active",
|
|
568
|
+
"sliver_type": "VM",
|
|
569
|
+
"ip_subnet": "192.168.1.0/24",
|
|
570
|
+
"error": None,
|
|
571
|
+
"image": "ubuntu-22.04",
|
|
572
|
+
"core": 4,
|
|
573
|
+
"ram": 8192,
|
|
574
|
+
"disk": 100,
|
|
575
|
+
"bandwidth": 10000,
|
|
576
|
+
"lease_start": "2025-05-01T12:00:00Z",
|
|
577
|
+
"lease_end": "2025-05-03T12:00:00Z",
|
|
578
|
+
"components": [
|
|
579
|
+
{
|
|
580
|
+
"component_id": "comp-01",
|
|
581
|
+
"node_id": "n1",
|
|
582
|
+
"component_node_id": "gpu-node-1",
|
|
583
|
+
"type": "GPU",
|
|
584
|
+
"model": "A100",
|
|
585
|
+
"bdfs": ["0000:65:00.0", "0000:65:00.1"]
|
|
586
|
+
}
|
|
587
|
+
],
|
|
588
|
+
"interfaces": [
|
|
589
|
+
{
|
|
590
|
+
"interface_id": "eth0",
|
|
591
|
+
"site": "RENC",
|
|
592
|
+
"vlan": "123",
|
|
593
|
+
"bdf": "0000:3b:00.0",
|
|
594
|
+
"local_name": "ens3",
|
|
595
|
+
"device_name": "mlx5_0",
|
|
596
|
+
"name": "mgmt-net"
|
|
597
|
+
}
|
|
598
|
+
]
|
|
599
|
+
}
|
|
600
|
+
"""
|
|
601
|
+
url = f"{self.base_url}/slivers/{slice_id}/{sliver_id}"
|
|
602
|
+
|
|
603
|
+
headers = self.headers.copy()
|
|
604
|
+
headers["Content-Type"] = "application/json"
|
|
605
|
+
|
|
606
|
+
response = requests.post(url, headers=headers, json=sliver_payload)
|
|
607
|
+
|
|
608
|
+
if response.status_code in (200, 201):
|
|
609
|
+
return response.json()
|
|
610
|
+
else:
|
|
611
|
+
raise Exception(f"Failed to post sliver: {response.status_code} - {response.text}")
|
|
612
|
+
|
|
613
|
+
def post_slice(self, slice_id: str, slice_payload: dict):
|
|
614
|
+
"""
|
|
615
|
+
Create or update a slice.
|
|
616
|
+
|
|
617
|
+
:param slice_id: UUID of the slice
|
|
618
|
+
:type slice_id: str
|
|
619
|
+
:param slice_payload: Dictionary containing the slice specification
|
|
620
|
+
:type slice_payload: dict
|
|
621
|
+
:return: Server response as a dictionary
|
|
622
|
+
:rtype: dict
|
|
623
|
+
|
|
624
|
+
Example slice_payload:
|
|
625
|
+
slice_payload = {
|
|
626
|
+
"project_id": "d78f130a-29b2-4f78-b9a9-87828d6cb2e2",
|
|
627
|
+
"user_id": "u98f124b-2332-483f-92b7-3bfbfb06b6e0",
|
|
628
|
+
"slice_id": "a3f41e9a-7e2b-4df7-baf7-12f48a3c8e6f",
|
|
629
|
+
"slice_name": "edge-ai-slice",
|
|
630
|
+
"state": "StableOK",
|
|
631
|
+
"lease_start": "2025-05-01T12:00:00Z",
|
|
632
|
+
"lease_end": "2025-05-03T12:00:00Z"
|
|
633
|
+
}
|
|
634
|
+
"""
|
|
635
|
+
url = f"{self.base_url}/slices/{slice_id}"
|
|
636
|
+
|
|
637
|
+
headers = self.headers.copy()
|
|
638
|
+
headers["Content-Type"] = "application/json"
|
|
639
|
+
|
|
640
|
+
response = requests.post(url, headers=headers, json=slice_payload)
|
|
641
|
+
|
|
642
|
+
if response.status_code in (200, 201):
|
|
643
|
+
return response.json()
|
|
644
|
+
else:
|
|
645
|
+
raise Exception(f"Failed to post slice: {response.status_code} - {response.text}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fabric_reports_client"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "FABRIC Reports Client API"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Komal Thareja", email = "kthare10@renci.org" }]
|
|
7
|
+
license = { text = "Apache-2.0" }
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
|
+
|
|
10
|
+
dependencies = ["requests"]
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["flint"]
|
|
14
|
+
build-backend = "flint.buildapi"
|
|
15
|
+
|
|
16
|
+
[tool.flint]
|
|
17
|
+
package = "fabric_reports_client"
|
|
18
|
+
|