pyfirecrest 2.2.0__tar.gz → 2.3.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.
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.github/workflows/testing.yml +1 -1
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/PKG-INFO +1 -1
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/AsyncClient.py +57 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/AsyncExternalStorage.py +2 -2
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/BasicClient.py +94 -29
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/__init__.py +1 -1
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/cli/__init__.py +57 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/types.py +23 -0
- pyfirecrest-2.3.0/firecrest/utilities.py +11 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_status.py +114 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_status_async.py +51 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_utilities.py +48 -1
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_utilities_async.py +22 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.github/workflows/python-publish.yml +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.gitignore +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.readthedocs.yml +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/LICENSE +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/README.md +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/async-example.md +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/Makefile +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/authorization.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/conf.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/index.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_async.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_basic.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_cli.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_index.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_async.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_basic.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_cli.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_errors.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_index.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_logging.rst +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/Authorization.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/ExternalStorage.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/FirecrestException.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/cli_script.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/path.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/py.typed +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/pyproject.toml +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/common.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/context.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_authorisation.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_compute.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_compute_async.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_extras.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_extras_async.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_reservation.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_reservation_async.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_storage.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_storage_async.py +0 -0
- {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tox.ini +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyfirecrest
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: pyFirecrest is a python wrapper for FirecREST
|
|
5
5
|
Author: CSCS Swiss National Supercomputing Center
|
|
6
6
|
Maintainer-email: Eirini Koutsaniti <eirini.koutsaniti@cscs.ch>, Juan Pablo Dorsch <juanpablo.dorsch@cscs.ch>
|
|
@@ -26,6 +26,7 @@ from packaging.version import Version, parse
|
|
|
26
26
|
import firecrest.FirecrestException as fe
|
|
27
27
|
import firecrest.types as t
|
|
28
28
|
from firecrest.AsyncExternalStorage import AsyncExternalUpload, AsyncExternalDownload
|
|
29
|
+
from firecrest.utilities import time_block
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
if sys.version_info >= (3, 8):
|
|
@@ -91,6 +92,12 @@ class AsyncFirecrest:
|
|
|
91
92
|
async def wrapper(*args, **kwargs):
|
|
92
93
|
client = args[0]
|
|
93
94
|
num_retries = 0
|
|
95
|
+
try:
|
|
96
|
+
f = kwargs["files"]["file"]
|
|
97
|
+
file_original_position = f[1].tell() if isinstance(f, tuple) else f.tell()
|
|
98
|
+
except KeyError:
|
|
99
|
+
file_original_position = None
|
|
100
|
+
|
|
94
101
|
resp = await func(*args, **kwargs)
|
|
95
102
|
while True:
|
|
96
103
|
if resp.status_code != client.TOO_MANY_REQUESTS_CODE:
|
|
@@ -110,6 +117,20 @@ class AsyncFirecrest:
|
|
|
110
117
|
default=resp.headers.get("RateLimit-Reset", default=10),
|
|
111
118
|
)
|
|
112
119
|
reset = int(reset)
|
|
120
|
+
try:
|
|
121
|
+
f = kwargs["files"]["file"]
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"Resetting the file pointer of the uploaded file "
|
|
124
|
+
f"to {file_original_position}"
|
|
125
|
+
)
|
|
126
|
+
if isinstance(f, tuple):
|
|
127
|
+
f[1].seek(file_original_position)
|
|
128
|
+
else:
|
|
129
|
+
f.seek(file_original_position)
|
|
130
|
+
|
|
131
|
+
except KeyError:
|
|
132
|
+
pass
|
|
133
|
+
|
|
113
134
|
microservice = kwargs["endpoint"].split("/")[1]
|
|
114
135
|
client = args[0]
|
|
115
136
|
logger.info(
|
|
@@ -569,6 +590,26 @@ class AsyncFirecrest:
|
|
|
569
590
|
resp = await self._get_request(endpoint="/status/parameters")
|
|
570
591
|
return self._json_response([resp], 200)["out"]
|
|
571
592
|
|
|
593
|
+
async def filesystems(self, system_name: Optional[str] = None) -> dict[str, List[t.Filesystem]]:
|
|
594
|
+
"""Returns the status of the filesystems per system.
|
|
595
|
+
|
|
596
|
+
:param system_name: the system name
|
|
597
|
+
:calls: GET `/status/filesystems`
|
|
598
|
+
:calls: GET `/status/filesystems/{system_name}`
|
|
599
|
+
|
|
600
|
+
.. warning:: This is available only for FirecREST>=1.15.0
|
|
601
|
+
"""
|
|
602
|
+
if system_name:
|
|
603
|
+
resp = await self._get_request(endpoint=f"/status/filesystems/{system_name}")
|
|
604
|
+
# Return the result in the same structure
|
|
605
|
+
result = {
|
|
606
|
+
system_name: self._json_response([resp], 200)["out"]
|
|
607
|
+
}
|
|
608
|
+
return result
|
|
609
|
+
else:
|
|
610
|
+
resp = await self._get_request(endpoint="/status/filesystems")
|
|
611
|
+
return self._json_response([resp], 200)["out"]
|
|
612
|
+
|
|
572
613
|
# Utilities
|
|
573
614
|
async def list_files(
|
|
574
615
|
self, machine: str, target_path: str, show_hidden: bool = False
|
|
@@ -953,6 +994,22 @@ class AsyncFirecrest:
|
|
|
953
994
|
# Invalid token, cannot retrieve username
|
|
954
995
|
return None
|
|
955
996
|
|
|
997
|
+
async def groups(self, machine) -> t.UserId:
|
|
998
|
+
"""Returns the output of the `id` command, user and group ids.
|
|
999
|
+
|
|
1000
|
+
:calls: GET `/utilities/whoami`
|
|
1001
|
+
|
|
1002
|
+
.. warning:: This is available only for FirecREST>=1.15.0
|
|
1003
|
+
"""
|
|
1004
|
+
resp = await self._get_request(
|
|
1005
|
+
endpoint="/utilities/whoami",
|
|
1006
|
+
additional_headers={"X-Machine-Name": machine},
|
|
1007
|
+
params={
|
|
1008
|
+
"groups": True
|
|
1009
|
+
}
|
|
1010
|
+
)
|
|
1011
|
+
return self._json_response([resp], 200)["output"]
|
|
1012
|
+
|
|
956
1013
|
# Compute
|
|
957
1014
|
async def submit(
|
|
958
1015
|
self,
|
|
@@ -229,7 +229,7 @@ class AsyncExternalDownload(AsyncExternalStorage):
|
|
|
229
229
|
:calls: GET `/tasks/{taskid}`
|
|
230
230
|
"""
|
|
231
231
|
if self._client._api_version > Version("1.13.0"):
|
|
232
|
-
return await self.object_storage_data["url"]
|
|
232
|
+
return (await self.object_storage_data)["url"]
|
|
233
233
|
else:
|
|
234
234
|
return await self.object_storage_data
|
|
235
235
|
|
|
@@ -240,7 +240,7 @@ class AsyncExternalDownload(AsyncExternalStorage):
|
|
|
240
240
|
|
|
241
241
|
:param target_path: the local path to save the file
|
|
242
242
|
"""
|
|
243
|
-
url = await self.
|
|
243
|
+
url = await self.object_storage_link
|
|
244
244
|
logger.info(f"Downloading the file from {url} and saving to {target_path}")
|
|
245
245
|
# LOCAL FIX FOR MAC
|
|
246
246
|
# url = url.replace("192.168.220.19", "localhost")
|
|
@@ -25,6 +25,7 @@ from packaging.version import Version, parse
|
|
|
25
25
|
import firecrest.FirecrestException as fe
|
|
26
26
|
import firecrest.types as t
|
|
27
27
|
from firecrest.ExternalStorage import ExternalUpload, ExternalDownload
|
|
28
|
+
from firecrest.utilities import time_block
|
|
28
29
|
|
|
29
30
|
if sys.version_info >= (3, 8):
|
|
30
31
|
from typing import Literal
|
|
@@ -64,6 +65,12 @@ class Firecrest:
|
|
|
64
65
|
def wrapper(*args, **kwargs):
|
|
65
66
|
client = args[0]
|
|
66
67
|
num_retries = 0
|
|
68
|
+
try:
|
|
69
|
+
f = kwargs["files"]["file"]
|
|
70
|
+
file_original_position = f[1].tell() if isinstance(f, tuple) else f.tell()
|
|
71
|
+
except KeyError:
|
|
72
|
+
file_original_position = None
|
|
73
|
+
|
|
67
74
|
resp = func(*args, **kwargs)
|
|
68
75
|
while True:
|
|
69
76
|
if resp.status_code != client.TOO_MANY_REQUESTS_CODE:
|
|
@@ -87,6 +94,20 @@ class Firecrest:
|
|
|
87
94
|
f"{reset} seconds and try again"
|
|
88
95
|
)
|
|
89
96
|
reset = int(reset)
|
|
97
|
+
try:
|
|
98
|
+
f = kwargs["files"]["file"]
|
|
99
|
+
logger.debug(
|
|
100
|
+
f"Resetting the file pointer of the uploaded file "
|
|
101
|
+
f"to {file_original_position}"
|
|
102
|
+
)
|
|
103
|
+
if isinstance(f, tuple):
|
|
104
|
+
f[1].seek(file_original_position)
|
|
105
|
+
else:
|
|
106
|
+
f.seek(file_original_position)
|
|
107
|
+
|
|
108
|
+
except KeyError:
|
|
109
|
+
pass
|
|
110
|
+
|
|
90
111
|
time.sleep(reset)
|
|
91
112
|
resp = func(*args, **kwargs)
|
|
92
113
|
num_retries += 1
|
|
@@ -137,13 +158,15 @@ class Firecrest:
|
|
|
137
158
|
headers.update(additional_headers)
|
|
138
159
|
|
|
139
160
|
logger.info(f"Making GET request to {endpoint}")
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
161
|
+
with time_block(f"GET request to {endpoint}", logger):
|
|
162
|
+
resp = self._session.get(
|
|
163
|
+
url=url,
|
|
164
|
+
headers=headers,
|
|
165
|
+
params=params,
|
|
166
|
+
verify=self._verify,
|
|
167
|
+
timeout=self.timeout,
|
|
168
|
+
)
|
|
169
|
+
|
|
147
170
|
return resp
|
|
148
171
|
|
|
149
172
|
@_retry_requests # type: ignore
|
|
@@ -156,14 +179,16 @@ class Firecrest:
|
|
|
156
179
|
headers.update(additional_headers)
|
|
157
180
|
|
|
158
181
|
logger.info(f"Making POST request to {endpoint}")
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
182
|
+
with time_block(f"POST request to {endpoint}", logger):
|
|
183
|
+
resp = self._session.post(
|
|
184
|
+
url=url,
|
|
185
|
+
headers=headers,
|
|
186
|
+
data=data,
|
|
187
|
+
files=files,
|
|
188
|
+
verify=self._verify,
|
|
189
|
+
timeout=self.timeout,
|
|
190
|
+
)
|
|
191
|
+
|
|
167
192
|
return resp
|
|
168
193
|
|
|
169
194
|
@_retry_requests # type: ignore
|
|
@@ -176,13 +201,15 @@ class Firecrest:
|
|
|
176
201
|
headers.update(additional_headers)
|
|
177
202
|
|
|
178
203
|
logger.info(f"Making PUT request to {endpoint}")
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
with time_block(f"PUT request to {endpoint}", logger):
|
|
205
|
+
resp = self._session.put(
|
|
206
|
+
url=url,
|
|
207
|
+
headers=headers,
|
|
208
|
+
data=data,
|
|
209
|
+
verify=self._verify,
|
|
210
|
+
timeout=self.timeout,
|
|
211
|
+
)
|
|
212
|
+
|
|
186
213
|
return resp
|
|
187
214
|
|
|
188
215
|
@_retry_requests # type: ignore
|
|
@@ -195,13 +222,15 @@ class Firecrest:
|
|
|
195
222
|
headers.update(additional_headers)
|
|
196
223
|
|
|
197
224
|
logger.info(f"Making DELETE request to {endpoint}")
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
225
|
+
with time_block(f"DELETE request to {endpoint}", logger):
|
|
226
|
+
resp = self._session.delete(
|
|
227
|
+
url=url,
|
|
228
|
+
headers=headers,
|
|
229
|
+
data=data,
|
|
230
|
+
verify=self._verify,
|
|
231
|
+
timeout=self.timeout,
|
|
232
|
+
)
|
|
233
|
+
|
|
205
234
|
return resp
|
|
206
235
|
|
|
207
236
|
@overload
|
|
@@ -397,6 +426,26 @@ class Firecrest:
|
|
|
397
426
|
resp = self._get_request(endpoint="/status/parameters")
|
|
398
427
|
return self._json_response([resp], 200)["out"]
|
|
399
428
|
|
|
429
|
+
def filesystems(self, system_name: Optional[str] = None) -> dict[str, List[t.Filesystem]]:
|
|
430
|
+
"""Returns the status of the filesystems per system.
|
|
431
|
+
|
|
432
|
+
:param system_name: the system name
|
|
433
|
+
:calls: GET `/status/filesystems`
|
|
434
|
+
:calls: GET `/status/filesystems/{system_name}`
|
|
435
|
+
|
|
436
|
+
.. warning:: This is available only for FirecREST>=1.15.0
|
|
437
|
+
"""
|
|
438
|
+
if system_name:
|
|
439
|
+
resp = self._get_request(endpoint=f"/status/filesystems/{system_name}")
|
|
440
|
+
# Return the result in the same structure
|
|
441
|
+
result = {
|
|
442
|
+
system_name: self._json_response([resp], 200)["out"]
|
|
443
|
+
}
|
|
444
|
+
return result
|
|
445
|
+
else:
|
|
446
|
+
resp = self._get_request(endpoint="/status/filesystems")
|
|
447
|
+
return self._json_response([resp], 200)["out"]
|
|
448
|
+
|
|
400
449
|
# Utilities
|
|
401
450
|
def list_files(
|
|
402
451
|
self, machine: str, target_path: str, show_hidden: bool = False
|
|
@@ -779,6 +828,22 @@ class Firecrest:
|
|
|
779
828
|
# Invalid token, cannot retrieve username
|
|
780
829
|
return None
|
|
781
830
|
|
|
831
|
+
def groups(self, machine) -> t.UserId:
|
|
832
|
+
"""Returns the output of the `id` command, user and group ids.
|
|
833
|
+
|
|
834
|
+
:calls: GET `/utilities/whoami`
|
|
835
|
+
|
|
836
|
+
.. warning:: This is available only for FirecREST>=1.15.0
|
|
837
|
+
"""
|
|
838
|
+
resp = self._get_request(
|
|
839
|
+
endpoint="/utilities/whoami",
|
|
840
|
+
additional_headers={"X-Machine-Name": machine},
|
|
841
|
+
params={
|
|
842
|
+
"groups": True
|
|
843
|
+
}
|
|
844
|
+
)
|
|
845
|
+
return self._json_response([resp], 200)["output"]
|
|
846
|
+
|
|
782
847
|
# Compute
|
|
783
848
|
def _submit_request(self, machine: str, job_script, local_file, account=None, env_vars=None):
|
|
784
849
|
data = {}
|
|
@@ -207,6 +207,35 @@ def parameters():
|
|
|
207
207
|
raise typer.Exit(code=1)
|
|
208
208
|
|
|
209
209
|
|
|
210
|
+
@app.command(rich_help_panel="Status commands")
|
|
211
|
+
def filesystems(
|
|
212
|
+
system: Optional[str] = typer.Option(
|
|
213
|
+
None,
|
|
214
|
+
"-s",
|
|
215
|
+
"--system",
|
|
216
|
+
help="The name of the system where the filesystems belongs to.",
|
|
217
|
+
envvar="FIRECREST_SYSTEM",
|
|
218
|
+
),
|
|
219
|
+
):
|
|
220
|
+
"""Information about the filesystems that are available through FirecREST"""
|
|
221
|
+
try:
|
|
222
|
+
result = client.filesystems(system)
|
|
223
|
+
for system in result.keys():
|
|
224
|
+
table = create_table(
|
|
225
|
+
f"Status of filesystems for `{system}`",
|
|
226
|
+
result[system],
|
|
227
|
+
("Name", "name"),
|
|
228
|
+
("Path", "path"),
|
|
229
|
+
("Status code", "status_code"),
|
|
230
|
+
("Status", "status"),
|
|
231
|
+
("Description", "description"),
|
|
232
|
+
)
|
|
233
|
+
console.print(table, overflow="fold")
|
|
234
|
+
except Exception as e:
|
|
235
|
+
examine_exeption(e)
|
|
236
|
+
raise typer.Exit(code=1)
|
|
237
|
+
|
|
238
|
+
|
|
210
239
|
@app.command(rich_help_panel="Status commands")
|
|
211
240
|
def tasks(
|
|
212
241
|
taskids: Optional[List[str]] = typer.Argument(
|
|
@@ -769,6 +798,34 @@ def whoami(
|
|
|
769
798
|
raise typer.Exit(code=1)
|
|
770
799
|
|
|
771
800
|
|
|
801
|
+
@app.command(rich_help_panel="Utilities commands")
|
|
802
|
+
def id(
|
|
803
|
+
config_from_parent: str = typer.Option(None,
|
|
804
|
+
callback=config_parent_load_callback,
|
|
805
|
+
is_eager=True,
|
|
806
|
+
hidden=True
|
|
807
|
+
),
|
|
808
|
+
system: str = typer.Option(
|
|
809
|
+
None,
|
|
810
|
+
"-s",
|
|
811
|
+
"--system",
|
|
812
|
+
help="The name of the system where the `id` command will run.",
|
|
813
|
+
envvar="FIRECREST_SYSTEM",
|
|
814
|
+
),
|
|
815
|
+
):
|
|
816
|
+
"""Return the identity of the user in the remote system.
|
|
817
|
+
"""
|
|
818
|
+
try:
|
|
819
|
+
result = client.groups(system)
|
|
820
|
+
user = f"{result['user']['id']}({result['user']['name']})"
|
|
821
|
+
group = f"{result['group']['id']}({result['group']['name']})"
|
|
822
|
+
all_groups = ",".join([f"{g['id']}({g['name']})" for g in result["groups"]])
|
|
823
|
+
console.print(f"uid={user} gid={group} groups={all_groups}")
|
|
824
|
+
except Exception as e:
|
|
825
|
+
examine_exeption(e)
|
|
826
|
+
raise typer.Exit(code=1)
|
|
827
|
+
|
|
828
|
+
|
|
772
829
|
class TransferType(str, Enum):
|
|
773
830
|
direct = "direct"
|
|
774
831
|
external = "external"
|
|
@@ -46,6 +46,16 @@ class System(TypedDict):
|
|
|
46
46
|
system: str
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class Filesystem(TypedDict):
|
|
50
|
+
"""A filesystem record, from `status/filesystems`"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
path: str
|
|
54
|
+
description: str
|
|
55
|
+
status: int
|
|
56
|
+
status_code: str
|
|
57
|
+
|
|
58
|
+
|
|
49
59
|
class Task(TypedDict):
|
|
50
60
|
"""A task record, from `/tasks`"""
|
|
51
61
|
|
|
@@ -140,3 +150,16 @@ class JobSubmit(TypedDict):
|
|
|
140
150
|
job_file_out: str
|
|
141
151
|
jobid: int
|
|
142
152
|
result: str
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class Id(TypedDict):
|
|
156
|
+
name: str
|
|
157
|
+
id: str
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class UserId(TypedDict):
|
|
161
|
+
"""A record from the `id` command"""
|
|
162
|
+
|
|
163
|
+
user: Id
|
|
164
|
+
group: Id
|
|
165
|
+
groups: list[Id]
|
|
@@ -2,6 +2,7 @@ import common
|
|
|
2
2
|
import json
|
|
3
3
|
import pytest
|
|
4
4
|
import re
|
|
5
|
+
import os
|
|
5
6
|
import test_authorisation as auth
|
|
6
7
|
|
|
7
8
|
from context import firecrest
|
|
@@ -60,6 +61,10 @@ def fc_server(httpserver):
|
|
|
60
61
|
parameters_handler
|
|
61
62
|
)
|
|
62
63
|
|
|
64
|
+
httpserver.expect_request(
|
|
65
|
+
re.compile("^/status/filesystems.*"), method="GET"
|
|
66
|
+
).respond_with_handler(filesystems_handler)
|
|
67
|
+
|
|
63
68
|
return httpserver
|
|
64
69
|
|
|
65
70
|
|
|
@@ -195,6 +200,49 @@ def parameters_handler(request: Request):
|
|
|
195
200
|
return Response(json.dumps(ret), status=200, content_type="application/json")
|
|
196
201
|
|
|
197
202
|
|
|
203
|
+
def filesystems_handler(request: Request):
|
|
204
|
+
if request.headers["Authorization"] != "Bearer VALID_TOKEN":
|
|
205
|
+
return Response(
|
|
206
|
+
json.dumps({"message": "Bad token; invalid JSON"}),
|
|
207
|
+
status=401,
|
|
208
|
+
content_type="application/json",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
ret = {
|
|
212
|
+
"description": "Filesystem information",
|
|
213
|
+
"out": {
|
|
214
|
+
"cluster": [
|
|
215
|
+
{
|
|
216
|
+
"description": "Users home filesystem",
|
|
217
|
+
"name": "HOME",
|
|
218
|
+
"path": "/home",
|
|
219
|
+
"status": "available",
|
|
220
|
+
"status_code": 200
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"description": "Scratch filesystem",
|
|
224
|
+
"name": "SCRATCH",
|
|
225
|
+
"path": "/scratch",
|
|
226
|
+
"status": "not available",
|
|
227
|
+
"status_code": 400
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
ret_status = 200
|
|
233
|
+
uri = request.url
|
|
234
|
+
if not uri.endswith("/status/filesystems"):
|
|
235
|
+
system = uri.split("/")[-1]
|
|
236
|
+
if system == "cluster":
|
|
237
|
+
ret["description"] = f"Filesystem information for system {system}"
|
|
238
|
+
ret["out"] = ret["out"][system]
|
|
239
|
+
else:
|
|
240
|
+
ret = {"description": f"System '{system}' does not exists."}
|
|
241
|
+
ret_status = 400
|
|
242
|
+
|
|
243
|
+
return Response(json.dumps(ret), status=ret_status, content_type="application/json")
|
|
244
|
+
|
|
245
|
+
|
|
198
246
|
def test_all_services(valid_client):
|
|
199
247
|
assert valid_client.all_services() == [
|
|
200
248
|
{
|
|
@@ -334,3 +382,69 @@ def test_cli_parameters(valid_credentials):
|
|
|
334
382
|
def test_parameters_invalid(invalid_client):
|
|
335
383
|
with pytest.raises(firecrest.UnauthorizedException):
|
|
336
384
|
invalid_client.parameters()
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def test_filesystems(valid_client):
|
|
388
|
+
assert valid_client.filesystems() == {
|
|
389
|
+
"cluster": [
|
|
390
|
+
{
|
|
391
|
+
"description": "Users home filesystem",
|
|
392
|
+
"name": "HOME",
|
|
393
|
+
"path": "/home",
|
|
394
|
+
"status": "available",
|
|
395
|
+
"status_code": 200
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
"description": "Scratch filesystem",
|
|
399
|
+
"name": "SCRATCH",
|
|
400
|
+
"path": "/scratch",
|
|
401
|
+
"status": "not available",
|
|
402
|
+
"status_code": 400
|
|
403
|
+
}
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
assert valid_client.filesystems(system_name="cluster") == {
|
|
408
|
+
"cluster": [
|
|
409
|
+
{
|
|
410
|
+
"description": "Users home filesystem",
|
|
411
|
+
"name": "HOME",
|
|
412
|
+
"path": "/home",
|
|
413
|
+
"status": "available",
|
|
414
|
+
"status_code": 200
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"description": "Scratch filesystem",
|
|
418
|
+
"name": "SCRATCH",
|
|
419
|
+
"path": "/scratch",
|
|
420
|
+
"status": "not available",
|
|
421
|
+
"status_code": 400
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_cli_filesystems(valid_credentials):
|
|
428
|
+
# Clean up the env var that may be set in the environment
|
|
429
|
+
os.environ.pop("FIRECREST_SYSTEM", None)
|
|
430
|
+
args = valid_credentials + ["filesystems"]
|
|
431
|
+
result = runner.invoke(cli.app, args=args)
|
|
432
|
+
stdout = common.clean_stdout(result.stdout)
|
|
433
|
+
assert result.exit_code == 0
|
|
434
|
+
assert "Status of filesystems for `cluster`" in stdout
|
|
435
|
+
|
|
436
|
+
args = valid_credentials + ["filesystems", "--system", "cluster"]
|
|
437
|
+
result = runner.invoke(cli.app, args=args)
|
|
438
|
+
stdout = common.clean_stdout(result.stdout)
|
|
439
|
+
assert result.exit_code == 0
|
|
440
|
+
assert "Status of filesystems for `cluster`" in stdout
|
|
441
|
+
|
|
442
|
+
args = valid_credentials + ["filesystems", "--system", "invalid_cluster"]
|
|
443
|
+
result = runner.invoke(cli.app, args=args)
|
|
444
|
+
stdout = common.clean_stdout(result.stdout)
|
|
445
|
+
assert result.exit_code == 1
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def test_filesystems_invalid(invalid_client):
|
|
449
|
+
with pytest.raises(firecrest.UnauthorizedException):
|
|
450
|
+
invalid_client.filesystems()
|
|
@@ -62,6 +62,10 @@ def fc_server(httpserver):
|
|
|
62
62
|
basic_status.parameters_handler
|
|
63
63
|
)
|
|
64
64
|
|
|
65
|
+
httpserver.expect_request(
|
|
66
|
+
re.compile("^/status/filesystems.*"), method="GET"
|
|
67
|
+
).respond_with_handler(basic_status.filesystems_handler)
|
|
68
|
+
|
|
65
69
|
return httpserver
|
|
66
70
|
|
|
67
71
|
|
|
@@ -167,3 +171,50 @@ async def test_parameters(valid_client):
|
|
|
167
171
|
async def test_parameters_invalid(invalid_client):
|
|
168
172
|
with pytest.raises(firecrest.UnauthorizedException):
|
|
169
173
|
await invalid_client.parameters()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.mark.asyncio
|
|
177
|
+
async def test_filesystems(valid_client):
|
|
178
|
+
assert await valid_client.filesystems() == {
|
|
179
|
+
"cluster": [
|
|
180
|
+
{
|
|
181
|
+
"description": "Users home filesystem",
|
|
182
|
+
"name": "HOME",
|
|
183
|
+
"path": "/home",
|
|
184
|
+
"status": "available",
|
|
185
|
+
"status_code": 200
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"description": "Scratch filesystem",
|
|
189
|
+
"name": "SCRATCH",
|
|
190
|
+
"path": "/scratch",
|
|
191
|
+
"status": "not available",
|
|
192
|
+
"status_code": 400
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
assert await valid_client.filesystems(system_name="cluster") == {
|
|
198
|
+
"cluster": [
|
|
199
|
+
{
|
|
200
|
+
"description": "Users home filesystem",
|
|
201
|
+
"name": "HOME",
|
|
202
|
+
"path": "/home",
|
|
203
|
+
"status": "available",
|
|
204
|
+
"status_code": 200
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"description": "Scratch filesystem",
|
|
208
|
+
"name": "SCRATCH",
|
|
209
|
+
"path": "/scratch",
|
|
210
|
+
"status": "not available",
|
|
211
|
+
"status_code": 400
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
async def test_filesystems_invalid(invalid_client):
|
|
219
|
+
with pytest.raises(firecrest.UnauthorizedException):
|
|
220
|
+
await invalid_client.filesystems()
|
|
@@ -730,8 +730,21 @@ def whoami_handler(request: Request):
|
|
|
730
730
|
content_type="application/json",
|
|
731
731
|
)
|
|
732
732
|
|
|
733
|
+
groups = request.args.get("groups", False)
|
|
734
|
+
if groups:
|
|
735
|
+
ret = {
|
|
736
|
+
"description": "User information",
|
|
737
|
+
"output": {
|
|
738
|
+
"group": {"id": "1000", "name": "group1"},
|
|
739
|
+
"groups": [{"id": "1000", "name": "group1"}, {"id": "1001", "name": "group2"}],
|
|
740
|
+
"user": {"id": "10000", "name": "test_user"},
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else:
|
|
744
|
+
ret = {"description": "Success on whoami operation.", "output": "username"}
|
|
745
|
+
|
|
733
746
|
return Response(
|
|
734
|
-
json.dumps(
|
|
747
|
+
json.dumps(ret),
|
|
735
748
|
status=200,
|
|
736
749
|
content_type="application/json",
|
|
737
750
|
)
|
|
@@ -1510,3 +1523,37 @@ def test_whoami_invalid_machine(valid_client):
|
|
|
1510
1523
|
def test_whoami_invalid_client(invalid_client):
|
|
1511
1524
|
with pytest.raises(firecrest.UnauthorizedException):
|
|
1512
1525
|
invalid_client.whoami("cluster1")
|
|
1526
|
+
|
|
1527
|
+
|
|
1528
|
+
def test_cli_whoami(valid_credentials):
|
|
1529
|
+
args = valid_credentials + ["whoami", "--system", "cluster1"]
|
|
1530
|
+
result = runner.invoke(cli.app, args=args)
|
|
1531
|
+
stdout = common.clean_stdout(result.stdout)
|
|
1532
|
+
assert result.exit_code == 0
|
|
1533
|
+
assert "username" in stdout
|
|
1534
|
+
|
|
1535
|
+
|
|
1536
|
+
def test_groups(valid_client):
|
|
1537
|
+
assert valid_client.groups("cluster1") == {
|
|
1538
|
+
"group": {"id": "1000", "name": "group1"},
|
|
1539
|
+
"groups": [{"id": "1000", "name": "group1"}, {"id": "1001", "name": "group2"}],
|
|
1540
|
+
"user": {"id": "10000", "name": "test_user"},
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
|
|
1544
|
+
def test_groups_invalid_machine(valid_client):
|
|
1545
|
+
with pytest.raises(firecrest.HeaderException):
|
|
1546
|
+
valid_client.groups("cluster2")
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
def test_groups_invalid_client(invalid_client):
|
|
1550
|
+
with pytest.raises(firecrest.UnauthorizedException):
|
|
1551
|
+
invalid_client.groups("cluster1")
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
def test_cli_id(valid_credentials):
|
|
1555
|
+
args = valid_credentials + ["id", "--system", "cluster1"]
|
|
1556
|
+
result = runner.invoke(cli.app, args=args)
|
|
1557
|
+
stdout = common.clean_stdout(result.stdout)
|
|
1558
|
+
assert result.exit_code == 0
|
|
1559
|
+
assert "uid=10000(test_user) gid=1000(group1) groups=1000(group1),1001(group2)" in stdout
|
|
@@ -651,3 +651,25 @@ async def test_whoami_invalid_machine(valid_client):
|
|
|
651
651
|
async def test_whoami_invalid_client(invalid_client):
|
|
652
652
|
with pytest.raises(firecrest.UnauthorizedException):
|
|
653
653
|
await invalid_client.whoami("cluster1")
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
@pytest.mark.asyncio
|
|
657
|
+
async def test_groups(valid_client):
|
|
658
|
+
assert await valid_client.groups("cluster1") == {
|
|
659
|
+
"group": {"id": "1000", "name": "group1"},
|
|
660
|
+
"groups": [{"id": "1000", "name": "group1"}, {"id": "1001", "name": "group2"}],
|
|
661
|
+
"user": {"id": "10000", "name": "test_user"},
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@pytest.mark.asyncio
|
|
666
|
+
async def test_groups_invalid_machine(valid_client):
|
|
667
|
+
with pytest.raises(firecrest.HeaderException):
|
|
668
|
+
await valid_client.groups("cluster2")
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
@pytest.mark.asyncio
|
|
672
|
+
async def test_groups_invalid_client(invalid_client):
|
|
673
|
+
with pytest.raises(firecrest.UnauthorizedException):
|
|
674
|
+
await invalid_client.groups("cluster1")
|
|
675
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|