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.
Files changed (52) hide show
  1. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.github/workflows/testing.yml +1 -1
  2. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/PKG-INFO +1 -1
  3. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/AsyncClient.py +57 -0
  4. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/AsyncExternalStorage.py +2 -2
  5. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/BasicClient.py +94 -29
  6. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/__init__.py +1 -1
  7. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/cli/__init__.py +57 -0
  8. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/types.py +23 -0
  9. pyfirecrest-2.3.0/firecrest/utilities.py +11 -0
  10. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_status.py +114 -0
  11. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_status_async.py +51 -0
  12. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_utilities.py +48 -1
  13. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_utilities_async.py +22 -0
  14. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.github/workflows/python-publish.yml +0 -0
  15. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.gitignore +0 -0
  16. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/.readthedocs.yml +0 -0
  17. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/LICENSE +0 -0
  18. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/README.md +0 -0
  19. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/async-example.md +0 -0
  20. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/Makefile +0 -0
  21. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/authorization.rst +0 -0
  22. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/conf.py +0 -0
  23. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/index.rst +0 -0
  24. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_async.rst +0 -0
  25. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_basic.rst +0 -0
  26. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_cli.rst +0 -0
  27. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/reference_index.rst +0 -0
  28. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_async.rst +0 -0
  29. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_basic.rst +0 -0
  30. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_cli.rst +0 -0
  31. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_errors.rst +0 -0
  32. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_index.rst +0 -0
  33. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/docs/source/tutorial_logging.rst +0 -0
  34. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/Authorization.py +0 -0
  35. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/ExternalStorage.py +0 -0
  36. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/FirecrestException.py +0 -0
  37. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/cli_script.py +0 -0
  38. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/path.py +0 -0
  39. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/firecrest/py.typed +0 -0
  40. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/pyproject.toml +0 -0
  41. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/common.py +0 -0
  42. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/context.py +0 -0
  43. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_authorisation.py +0 -0
  44. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_compute.py +0 -0
  45. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_compute_async.py +0 -0
  46. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_extras.py +0 -0
  47. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_extras_async.py +0 -0
  48. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_reservation.py +0 -0
  49. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_reservation_async.py +0 -0
  50. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_storage.py +0 -0
  51. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tests/test_storage_async.py +0 -0
  52. {pyfirecrest-2.2.0 → pyfirecrest-2.3.0}/tox.ini +0 -0
@@ -12,7 +12,7 @@ jobs:
12
12
  runs-on: ubuntu-latest
13
13
  strategy:
14
14
  matrix:
15
- python-version: ["3.7", "3.8", "3.9"]
15
+ python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
16
16
 
17
17
  steps:
18
18
  - uses: actions/checkout@v2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfirecrest
3
- Version: 2.2.0
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.object_storage_data
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
- resp = self._session.get(
141
- url=url,
142
- headers=headers,
143
- params=params,
144
- verify=self._verify,
145
- timeout=self.timeout,
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
- resp = self._session.post(
160
- url=url,
161
- headers=headers,
162
- data=data,
163
- files=files,
164
- verify=self._verify,
165
- timeout=self.timeout,
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
- resp = self._session.put(
180
- url=url,
181
- headers=headers,
182
- data=data,
183
- verify=self._verify,
184
- timeout=self.timeout,
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
- resp = self._session.delete(
199
- url=url,
200
- headers=headers,
201
- data=data,
202
- verify=self._verify,
203
- timeout=self.timeout,
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 = {}
@@ -7,7 +7,7 @@
7
7
  import sys
8
8
 
9
9
 
10
- __version__ = "2.2.0"
10
+ __version__ = "2.3.0"
11
11
  __app_name__ = "firecrest"
12
12
  MIN_PYTHON_VERSION = (3, 7, 0)
13
13
 
@@ -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]
@@ -0,0 +1,11 @@
1
+ import time
2
+ from contextlib import contextmanager
3
+
4
+ @contextmanager
5
+ def time_block(label, logger):
6
+ start_time = time.time()
7
+ try:
8
+ yield
9
+ finally:
10
+ end_time = time.time()
11
+ logger.debug(f"{label} took {end_time - start_time:.6f} seconds")
@@ -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({"description": "Success on whoami operation.", "output": "username"}),
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