ctao-bdms-clients 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

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,100 @@
1
+ import subprocess as sp
2
+ from pathlib import Path
3
+
4
+ import pytest
5
+ from rucio.client.rseclient import RSEClient
6
+
7
+ from .conftest import STORAGE_HOSTNAME, STORAGE_PROTOCOL
8
+
9
+ # Constants for RSEs and expected attributes
10
+ RSE_CONFIG = {
11
+ "STORAGE-1": {"ONSITE": True, "OFFSITE": None},
12
+ "STORAGE-2": {"ONSITE": None, "OFFSITE": True},
13
+ "STORAGE-3": {"ONSITE": None, "OFFSITE": True},
14
+ }
15
+
16
+
17
+ def test_shared_storage(storage_mount_path: Path) -> Path:
18
+ """Ensure shared storage directory exists before any test runs"""
19
+ assert (
20
+ storage_mount_path.exists()
21
+ ), f"Shared storage {storage_mount_path} is not available on the client"
22
+
23
+
24
+ def test_file_access_from_onsite_storage_using_gfal(
25
+ storage_mount_path: Path, onsite_test_file: tuple[Path, str]
26
+ ):
27
+ """Verify that the file is accessible from the onsite storage pod using gfal-ls"""
28
+ test_file_path, _ = onsite_test_file
29
+ test_file_lfn = f"/{test_file_path.relative_to(storage_mount_path)}"
30
+ test_file_name = test_file_path.name
31
+
32
+ gfal_url = f"{STORAGE_PROTOCOL}://{STORAGE_HOSTNAME}/rucio/{test_file_lfn}"
33
+ cmd = ["gfal-ls", gfal_url]
34
+
35
+ ret = sp.run(cmd, capture_output=True, text=True)
36
+ stdout = ret.stdout.strip()
37
+ stderr = ret.stderr.strip()
38
+ msg = f"gfal-ls failed for {gfal_url}:\nSTDERR: {stderr}\nSTDOUT: {stderr}"
39
+ assert ret.returncode == 0, msg
40
+
41
+ msg = f"File {test_file_name} not accessible; gfal-ls output: {stdout!r}"
42
+ assert any(test_file_name in line for line in stdout.splitlines()), msg
43
+
44
+
45
+ @pytest.mark.usefixtures("_auth_proxy")
46
+ def test_rse_attributes():
47
+ """Verify onsite and offsite RSE attributes set by setup_rucio.sh during the bootstrap job deployment
48
+
49
+ Ensures:
50
+ - STORAGE-1 has onsite=True and no offsite=True
51
+ - STORAGE-2 and STORAGE-3 have offsite=True and no onsite=True
52
+
53
+ Raises:
54
+ pytest.fail: If RSE details cannot be retrieved (in case of RSEs not found or Rucio server connectivity issues)
55
+ AssertionError: If attribute values don't match the expected ones
56
+ """
57
+
58
+ rse_client = RSEClient()
59
+
60
+ for rse_name, expected_attrs in RSE_CONFIG.items():
61
+ try:
62
+ # Verify RSE exists
63
+ rse_details = rse_client.get_rse(rse_name)
64
+ print(f"{rse_name} metadata: {rse_details}")
65
+
66
+ # Fetch attributes
67
+ attrs = rse_client.list_rse_attributes(rse_name)
68
+ print(f"{rse_name} attributes: {attrs}")
69
+
70
+ # Verify RSE onsite attribute
71
+ onsite_value = attrs.get("ONSITE")
72
+ expected_onsite = expected_attrs["ONSITE"]
73
+ assert onsite_value == expected_onsite, (
74
+ f"{rse_name} onsite attribute mismatch: "
75
+ f"expected {expected_onsite!r}, got {onsite_value!r}. "
76
+ f"Full attributes: {attrs}"
77
+ )
78
+
79
+ # Verify RSE offsite attribute
80
+ offsite_value = attrs.get("OFFSITE")
81
+ expected_offsite = expected_attrs["OFFSITE"]
82
+ if expected_offsite is None:
83
+ assert offsite_value is not True, (
84
+ f"{rse_name} should not have offsite=True, "
85
+ f"got {offsite_value!r}. Full attributes: {attrs}"
86
+ )
87
+ else:
88
+ assert offsite_value == expected_offsite, (
89
+ f"{rse_name} offsite attribute mismatch: "
90
+ f"expected {expected_offsite!r}, got {offsite_value!r}. "
91
+ f"Full attributes: {attrs}"
92
+ )
93
+
94
+ print(f"{rse_name} passed attribute tests")
95
+
96
+ except Exception as e:
97
+ pytest.fail(
98
+ f"Failed to retrieve RSE details for {rse_name}: {str(e)}. "
99
+ "Check Rucio server connectivity or RSE existence"
100
+ )
bdms/tests/utils.py ADDED
@@ -0,0 +1,130 @@
1
+ """Utility functions for BDMS tests."""
2
+
3
+ import logging
4
+ import os
5
+ import time
6
+ from pathlib import Path
7
+
8
+ from dotenv import load_dotenv
9
+ from minio import Minio
10
+ from rucio.client.ruleclient import RuleClient
11
+ from rucio.common.exception import RucioException
12
+
13
+ # Default timeout and polling interval (in seconds) for waiting for replication
14
+ DEFAULT_TIMEOUT = 1000
15
+ DEFAULT_POLL_INTERVAL = 30
16
+ XROOTD_UID = int(os.getenv("XROOTD_UID", 994))
17
+ XROOTD_GID = int(os.getenv("XROOTD_GID", 994))
18
+ LOGGER = logging.getLogger(__name__)
19
+
20
+
21
+ def reset_xrootd_permissions(path):
22
+ recursive_chown(path, uid=XROOTD_UID, gid=XROOTD_GID)
23
+
24
+
25
+ def recursive_chown(path: Path, uid: int, gid: int):
26
+ """Equivalent of unix chmod -R <uid>:<gid> <path>."""
27
+ os.chown(path, uid, gid)
28
+
29
+ for root, dirs, files in os.walk(path):
30
+ root = Path(root)
31
+ for d in dirs:
32
+ os.chown(root / d, uid, gid)
33
+ for f in files:
34
+ os.chown(root / f, uid, gid)
35
+
36
+
37
+ def wait_for_replication_status(
38
+ rule_client: RuleClient,
39
+ rule_id: str,
40
+ expected_status: str = "OK",
41
+ timeout: int = DEFAULT_TIMEOUT,
42
+ poll_interval: int = DEFAULT_POLL_INTERVAL,
43
+ logger: logging.Logger = None,
44
+ ) -> None:
45
+ if logger is None:
46
+ logger = LOGGER
47
+
48
+ start_time = time.perf_counter()
49
+ current_status = None
50
+ result = None
51
+ max_retries = 3
52
+
53
+ while (time.perf_counter() - start_time) < timeout:
54
+ retries = 0
55
+ while retries < max_retries:
56
+ try:
57
+ result = rule_client.get_replication_rule(rule_id)
58
+ current_status = result["state"]
59
+ break
60
+ except RucioException as e:
61
+ retries += 1
62
+ if retries == max_retries:
63
+ raise RuntimeError(
64
+ f"Failed to check replication rule status for rule {rule_id} after {max_retries} retries: {str(e)}"
65
+ ) from e
66
+ logger.warning(
67
+ "Failed to check rule %s status (attempt %s/%s): %s. Retrying...",
68
+ rule_id,
69
+ retries,
70
+ max_retries,
71
+ str(e),
72
+ )
73
+ time.sleep(1)
74
+
75
+ if current_status == expected_status:
76
+ logger.info(
77
+ "Replication rule %s reached status '%s'", rule_id, expected_status
78
+ )
79
+ return
80
+
81
+ logger.debug(
82
+ "Rule %s is in state %s, waiting for %s (elapsed: %.2f seconds)",
83
+ rule_id,
84
+ current_status,
85
+ expected_status,
86
+ time.perf_counter() - start_time,
87
+ )
88
+ time.sleep(poll_interval)
89
+
90
+ msg = (
91
+ f"Replication rule {rule_id} did not reach status '{expected_status}' within {timeout} seconds. "
92
+ f"Current status is '{current_status}'.\nFull output: {result}"
93
+ )
94
+ raise TimeoutError(msg)
95
+
96
+
97
+ TEST_DATA_DIR = Path(os.getenv("BDMS_TEST_DATA_DIR", "test_data")).absolute()
98
+
99
+
100
+ def download_test_file(path):
101
+ """Get a FITS file from the MinIO server"""
102
+
103
+ load_dotenv()
104
+
105
+ access_key = os.environ["MINIO_ACCESS_KEY"]
106
+ secret_key = os.environ["MINIO_SECRET_KEY"]
107
+
108
+ output_path = TEST_DATA_DIR / path
109
+
110
+ TEST_DATA_DIR.mkdir(parents=True, exist_ok=True)
111
+
112
+ if not output_path.exists():
113
+ client = Minio(
114
+ "minio-cta.zeuthen.desy.de",
115
+ access_key,
116
+ secret_key,
117
+ secure=True,
118
+ )
119
+
120
+ LOGGER.info("Downloading %s", path)
121
+ client.fget_object(
122
+ "dpps-data-private",
123
+ path,
124
+ output_path,
125
+ )
126
+
127
+ else:
128
+ LOGGER.info("File %s already exists, skipping download", output_path)
129
+
130
+ return output_path
@@ -1,31 +1,38 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ctao-bdms-clients
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Client module for the CTAO DPPS Bulk Data Management System
5
5
  Author-email: Georgios Zacharis <georgios.zacharis@inaf.it>, Stefano Gallozzi <Stefano.gallozzi@inaf.it>, Michele Mastropietro <michele.mastropietro@inaf.it>, Syed Anwar Ul Hasan <syedanwarul.hasan@cta-consortium.org>, Maximilian Linhoff <maximilian.linhoff@cta-observatory.org>, Volodymyr Savchenko <Volodymyr.Savchenko@epfl.ch>
6
- License: BSD-3-Clause
6
+ License-Expression: BSD-3-Clause
7
7
  Project-URL: repository, https://gitlab.cta-observatory.org/cta-computing/dpps/bdms/bdms
8
8
  Project-URL: documentation, http://cta-computing.gitlab-pages.cta-observatory.org/dpps/bdms/bdms
9
9
  Requires-Python: >=3.9
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: rucio-clients~=35.4.1
13
- Requires-Dist: ctao-bdms-rucio-policy==0.1.0
12
+ Requires-Dist: astropy<8.0.0a0,>=6.0.1
13
+ Requires-Dist: ctao-bdms-rucio-policy~=0.1.0
14
+ Requires-Dist: rucio-clients~=35.7.0
15
+ Requires-Dist: protozfits>=2.7.2
14
16
  Provides-Extra: test
15
17
  Requires-Dist: pytest; extra == "test"
16
18
  Requires-Dist: pytest-cov; extra == "test"
17
19
  Requires-Dist: pytest-requirements; extra == "test"
20
+ Requires-Dist: python-dotenv; extra == "test"
21
+ Requires-Dist: minio; extra == "test"
22
+ Requires-Dist: pytest-xdist; extra == "test"
18
23
  Provides-Extra: doc
19
24
  Requires-Dist: sphinx; extra == "doc"
20
25
  Requires-Dist: numpydoc; extra == "doc"
21
26
  Requires-Dist: ctao-sphinx-theme; extra == "doc"
22
27
  Requires-Dist: myst-parser; extra == "doc"
23
28
  Requires-Dist: sphinx-changelog; extra == "doc"
29
+ Requires-Dist: sphinx-automodapi; extra == "doc"
24
30
  Provides-Extra: dev
25
31
  Requires-Dist: setuptools_scm; extra == "dev"
26
32
  Requires-Dist: sphinx-autobuild; extra == "dev"
27
33
  Provides-Extra: all
28
34
  Requires-Dist: bdms[dev,doc,test]; extra == "all"
35
+ Dynamic: license-file
29
36
 
30
37
  # Bulk Data Management System
31
38
 
@@ -0,0 +1,20 @@
1
+ bdms/__init__.py,sha256=7btE6tNhFqXSv2eUhZ-0m1J3nTTs4Xo6HWcQI4eh5Do,142
2
+ bdms/_version.py,sha256=iB5DfB5V6YB5Wo4JmvS-txT42QtmGaWcWp3udRT7zCI,511
3
+ bdms/acada_ingestion.py,sha256=L-LBdfd7dbSbW0poseXsZ8CbgWch8j57yaQncIemnOs,17671
4
+ bdms/extract_fits_metadata.py,sha256=ZGJQCFJCXkWg8N3CAb17GB-wwPj-wTvNg0JOS-MemZ0,3431
5
+ bdms/version.py,sha256=mTfi1WzbIs991NyImM6mcMg1R39a6U1W2pKnk-Tt5Vw,765
6
+ bdms/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ bdms/tests/conftest.py,sha256=TqMBSExgO4omUHVqoXcI1AOhS3F8de-03275IlYEW6k,3896
8
+ bdms/tests/test_acada_ingestion.py,sha256=u27Srhm8kXKtFoPr5gQdZeGDjrlECMr9ysWwGfs2w_Q,18277
9
+ bdms/tests/test_basic_rucio_functionality.py,sha256=9GIX8IO6wBJm40LKFEH2StS-fMKvC07sxFHPVR7dftU,3583
10
+ bdms/tests/test_dpps_rel_0_0.py,sha256=MnbuBoS_kUUiMcHE3-jqOzekQNUa-wcsjCJqJQ2J9S4,2957
11
+ bdms/tests/test_extract_fits_metadata.py,sha256=A935WD2TF3lBcaeDmzGSlH2IXUF1v8qslrsW30lnEAA,3490
12
+ bdms/tests/test_file_replicas.py,sha256=NqutrSJa5ME50JpmyATNPSLqq1AOq1ruv84XSY3PKLI,2635
13
+ bdms/tests/test_metadata.py,sha256=f0tSqNGlYe-ydoSDJw0k1De2kHoPl6g-GYBj_jP6kCY,3728
14
+ bdms/tests/test_onsite_storage.py,sha256=waK7t9kBquzJbuLLYcpeNU9YuA70XTRS88RMxBWxawI,3765
15
+ bdms/tests/utils.py,sha256=4g7__ms-xnTyyBKMlmV4hpC505V6uVaXJDi9XQ8UC_4,3717
16
+ ctao_bdms_clients-0.2.0.dist-info/licenses/LICENSE,sha256=Py9riZY_f0CmXbrZ5JreE3WgglyWkRnwUfqydvX6jxE,1556
17
+ ctao_bdms_clients-0.2.0.dist-info/METADATA,sha256=Rstsh4x_9neUhLc8mFSOA8iY4YR0Lvrn7IRVWcROqXI,2383
18
+ ctao_bdms_clients-0.2.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
19
+ ctao_bdms_clients-0.2.0.dist-info/top_level.txt,sha256=ao0U8aA33KRHpcqmr7yrK8y2AQ6ahSu514tfaN4hDV8,5
20
+ ctao_bdms_clients-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,14 +0,0 @@
1
- bdms/__init__.py,sha256=7btE6tNhFqXSv2eUhZ-0m1J3nTTs4Xo6HWcQI4eh5Do,142
2
- bdms/_version.py,sha256=-LyU5F1uZDjn6Q8_Z6-_FJt_8RE4Kq9zcKdg1abSSps,511
3
- bdms/version.py,sha256=mTfi1WzbIs991NyImM6mcMg1R39a6U1W2pKnk-Tt5Vw,765
4
- bdms/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- bdms/tests/conftest.py,sha256=vN96UXhO2503svkg3V2jxfq5SSUtlRCWZt5MQgblu7Y,1374
6
- bdms/tests/test_basic_rucio_functionality.py,sha256=Sm6Kif8g5xPaw6tIIJ1TavDxVje2YaYYC9PnhVbVQFI,3999
7
- bdms/tests/test_dpps_rel_0_0.py,sha256=MnbuBoS_kUUiMcHE3-jqOzekQNUa-wcsjCJqJQ2J9S4,2957
8
- bdms/tests/test_file_replicas.py,sha256=NqutrSJa5ME50JpmyATNPSLqq1AOq1ruv84XSY3PKLI,2635
9
- bdms/tests/test_metadata.py,sha256=f0tSqNGlYe-ydoSDJw0k1De2kHoPl6g-GYBj_jP6kCY,3728
10
- ctao_bdms_clients-0.1.0.dist-info/LICENSE,sha256=Py9riZY_f0CmXbrZ5JreE3WgglyWkRnwUfqydvX6jxE,1556
11
- ctao_bdms_clients-0.1.0.dist-info/METADATA,sha256=hUWNWoucRs9--Z2TXYn-IMZsaOzw13fR9kqmpKvIkgQ,2100
12
- ctao_bdms_clients-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
13
- ctao_bdms_clients-0.1.0.dist-info/top_level.txt,sha256=ao0U8aA33KRHpcqmr7yrK8y2AQ6ahSu514tfaN4hDV8,5
14
- ctao_bdms_clients-0.1.0.dist-info/RECORD,,