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.
- bdms/_version.py +2 -2
- bdms/acada_ingestion.py +479 -0
- bdms/extract_fits_metadata.py +134 -0
- bdms/tests/conftest.py +64 -0
- bdms/tests/test_acada_ingestion.py +526 -0
- bdms/tests/test_basic_rucio_functionality.py +14 -27
- bdms/tests/test_extract_fits_metadata.py +97 -0
- bdms/tests/test_onsite_storage.py +100 -0
- bdms/tests/utils.py +130 -0
- {ctao_bdms_clients-0.1.0.dist-info → ctao_bdms_clients-0.2.0.dist-info}/METADATA +12 -5
- ctao_bdms_clients-0.2.0.dist-info/RECORD +20 -0
- {ctao_bdms_clients-0.1.0.dist-info → ctao_bdms_clients-0.2.0.dist-info}/WHEEL +1 -1
- ctao_bdms_clients-0.1.0.dist-info/RECORD +0 -14
- {ctao_bdms_clients-0.1.0.dist-info → ctao_bdms_clients-0.2.0.dist-info/licenses}/LICENSE +0 -0
- {ctao_bdms_clients-0.1.0.dist-info → ctao_bdms_clients-0.2.0.dist-info}/top_level.txt +0 -0
@@ -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.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ctao-bdms-clients
|
3
|
-
Version: 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:
|
13
|
-
Requires-Dist: ctao-bdms-rucio-policy
|
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,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,,
|
File without changes
|
File without changes
|