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.
@@ -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.
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.1
2
+ Name: fabric_reports_client
3
+ Version: 0.1.0
4
+ Summary: FABRIC Reports Client API
5
+ Author-email: Komal Thareja <kthare10@renci.org>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: requests
9
+
10
+
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
+