ctao-bdms-clients 0.2.0rc1__py3-none-any.whl → 0.3.0rc1__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_ingest_cli.py +400 -0
- bdms/acada_ingestion.py +528 -17
- bdms/extract_fits_metadata.py +134 -0
- bdms/tests/conftest.py +157 -14
- bdms/tests/test_acada_ingest_cli.py +279 -0
- bdms/tests/test_acada_ingestion.py +1315 -98
- bdms/tests/test_basic_rucio_functionality.py +0 -1
- bdms/tests/test_dpps_rel_0_0.py +6 -0
- bdms/tests/test_extract_fits_metadata.py +97 -0
- bdms/tests/test_onsite_storage.py +16 -35
- bdms/tests/utils.py +28 -0
- {ctao_bdms_clients-0.2.0rc1.dist-info → ctao_bdms_clients-0.3.0rc1.dist-info}/METADATA +8 -2
- ctao_bdms_clients-0.3.0rc1.dist-info/RECORD +23 -0
- {ctao_bdms_clients-0.2.0rc1.dist-info → ctao_bdms_clients-0.3.0rc1.dist-info}/WHEEL +1 -1
- ctao_bdms_clients-0.3.0rc1.dist-info/entry_points.txt +2 -0
- ctao_bdms_clients-0.2.0rc1.dist-info/RECORD +0 -18
- {ctao_bdms_clients-0.2.0rc1.dist-info → ctao_bdms_clients-0.3.0rc1.dist-info}/licenses/LICENSE +0 -0
- {ctao_bdms_clients-0.2.0rc1.dist-info → ctao_bdms_clients-0.3.0rc1.dist-info}/top_level.txt +0 -0
bdms/tests/test_dpps_rel_0_0.py
CHANGED
@@ -88,3 +88,9 @@ def test_retrieve_by_lfn(test_scope, generic_data_product, tmp_path):
|
|
88
88
|
expected_path = tmp_path / test_scope / test_lfn.lstrip("/")
|
89
89
|
assert expected_path.is_file(), "File not downloaded to expected location"
|
90
90
|
assert expected_path.read_text() == expected_content
|
91
|
+
|
92
|
+
|
93
|
+
@pytest.mark.verifies_usecase("DPPS-UC-110-1.3.1")
|
94
|
+
@pytest.mark.xfail
|
95
|
+
def test_retrieve_by_lfn_with_iam(test_scope, generic_data_product, tmp_path):
|
96
|
+
raise NotImplementedError
|
@@ -0,0 +1,97 @@
|
|
1
|
+
from astropy.io import fits
|
2
|
+
|
3
|
+
from bdms.extract_fits_metadata import (
|
4
|
+
extract_metadata_from_data,
|
5
|
+
extract_metadata_from_headers,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
def test_extraction_correct_value_subarray_file(subarray_test_file):
|
10
|
+
"""Test the extraction of metadata from a FITS file."""
|
11
|
+
with fits.open(subarray_test_file) as hdul:
|
12
|
+
metadata_header = extract_metadata_from_headers(hdul)
|
13
|
+
|
14
|
+
metadata_payload = extract_metadata_from_data(subarray_test_file)
|
15
|
+
metadata_fits = {**metadata_header, **metadata_payload}
|
16
|
+
|
17
|
+
assert len(metadata_fits) > 0, "No metadata found in the SUBARRAY FITS"
|
18
|
+
|
19
|
+
expected_keys_in_fits_file = {
|
20
|
+
"observatory": "CTA",
|
21
|
+
"start_time": "2025-02-04T21:34:05",
|
22
|
+
"end_time": "2025-02-04T21:43:12",
|
23
|
+
"subarray_id": 0,
|
24
|
+
"sb_id": 2000000066,
|
25
|
+
"obs_id": 2000000200,
|
26
|
+
}
|
27
|
+
|
28
|
+
for key, value in expected_keys_in_fits_file.items():
|
29
|
+
assert metadata_fits[key] == value, f"Expected key '{key}' not found."
|
30
|
+
|
31
|
+
|
32
|
+
def test_extraction_correct_value_tel_trigger_file(tel_trigger_test_file):
|
33
|
+
"""Test the extraction of metadata from a FITS file."""
|
34
|
+
with fits.open(tel_trigger_test_file) as hdul:
|
35
|
+
metadata_header = extract_metadata_from_headers(hdul)
|
36
|
+
|
37
|
+
metadata_payload = extract_metadata_from_data(tel_trigger_test_file)
|
38
|
+
metadata_fits = {**metadata_header, **metadata_payload}
|
39
|
+
|
40
|
+
assert len(metadata_fits) > 0, "No metadata found in the Telescope TRIGGER FITS"
|
41
|
+
|
42
|
+
expected_keys_in_fits_file = {
|
43
|
+
"observatory": "CTA",
|
44
|
+
"start_time": "2025-02-04T21:34:05",
|
45
|
+
"end_time": "2025-02-04T21:43:11",
|
46
|
+
"tel_ids": [1],
|
47
|
+
"sb_id": 2000000066,
|
48
|
+
"obs_id": 2000000200,
|
49
|
+
}
|
50
|
+
|
51
|
+
for key, value in expected_keys_in_fits_file.items():
|
52
|
+
assert metadata_fits[key] == value, f"Expected key '{key}' not found."
|
53
|
+
|
54
|
+
|
55
|
+
def test_extraction_correct_value_tel_events_file(tel_events_test_file):
|
56
|
+
"""Test the extraction of metadata from a FITS file."""
|
57
|
+
with fits.open(tel_events_test_file) as hdul:
|
58
|
+
metadata_header = extract_metadata_from_headers(hdul)
|
59
|
+
|
60
|
+
metadata_payload = extract_metadata_from_data(tel_events_test_file)
|
61
|
+
metadata_fits = {**metadata_header, **metadata_payload}
|
62
|
+
|
63
|
+
assert len(metadata_fits) > 0, "No metadata found in the Telescope EVENTS FITS"
|
64
|
+
|
65
|
+
expected_keys_in_fits_file = {
|
66
|
+
"observatory": "CTA",
|
67
|
+
"start_time": "2025-04-01T15:25:02",
|
68
|
+
"end_time": "2025-04-01T15:25:03",
|
69
|
+
"sb_id": 0,
|
70
|
+
"obs_id": 0,
|
71
|
+
}
|
72
|
+
|
73
|
+
for key, value in expected_keys_in_fits_file.items():
|
74
|
+
assert metadata_fits[key] == value, f"Expected key '{key}' not found."
|
75
|
+
|
76
|
+
|
77
|
+
def test_extract_metadata_from_data_incorrect_header(tmp_path):
|
78
|
+
"""Test the extraction of metadata from an empty FITS file header."""
|
79
|
+
fits_file_path = tmp_path / "empty_fits.fits.fz"
|
80
|
+
hdul = fits.HDUList([fits.PrimaryHDU()])
|
81
|
+
hdul.writeto(fits_file_path, checksum=True)
|
82
|
+
|
83
|
+
with fits.open(fits_file_path) as hdul:
|
84
|
+
metadata = extract_metadata_from_headers(hdul)
|
85
|
+
|
86
|
+
assert metadata == {}, "Expected empty metadata in the header"
|
87
|
+
|
88
|
+
|
89
|
+
def test_extract_metadata_from_data_incorrect_data(tmp_path):
|
90
|
+
"""Test the extraction of metadata from an empty FITS file data."""
|
91
|
+
fits_file_path = tmp_path / "empty_fits.fits.fz"
|
92
|
+
hdul = fits.HDUList([fits.PrimaryHDU()])
|
93
|
+
hdul.writeto(fits_file_path, checksum=True)
|
94
|
+
|
95
|
+
metadata = extract_metadata_from_data(fits_file_path)
|
96
|
+
|
97
|
+
assert metadata == {}, "Expected empty metadata in the payload"
|
@@ -1,8 +1,5 @@
|
|
1
|
-
import os
|
2
1
|
import subprocess as sp
|
3
|
-
from datetime import datetime
|
4
2
|
from pathlib import Path
|
5
|
-
from secrets import token_hex
|
6
3
|
|
7
4
|
import pytest
|
8
5
|
from rucio.client.rseclient import RSEClient
|
@@ -24,41 +21,25 @@ def test_shared_storage(storage_mount_path: Path) -> Path:
|
|
24
21
|
), f"Shared storage {storage_mount_path} is not available on the client"
|
25
22
|
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
unique_id = f"{datetime.now():%Y%m%d_%H%M%S}_{token_hex(8)}"
|
31
|
-
test_file_name = f"/ctao.dpps.test/{test_scope}/testfile_{unique_id}.txt"
|
32
|
-
test_file_path = storage_mount_path / test_file_name.lstrip("/")
|
33
|
-
test_file_content = f"This is a test file {unique_id}"
|
34
|
-
test_file_path.parent.mkdir(parents=True, exist_ok=True)
|
35
|
-
test_file_path.write_text(test_file_content)
|
36
|
-
assert test_file_path.exists(), f"Test file {test_file_path} was not created successfully at {storage_mount_path}"
|
37
|
-
|
38
|
-
return test_file_name, test_file_content
|
39
|
-
|
40
|
-
|
41
|
-
def test_file_access_from_onsite_storage_using_gfal(test_file: tuple[Path, str]):
|
24
|
+
def test_file_access_from_onsite_storage_using_gfal(
|
25
|
+
storage_mount_path: Path, onsite_test_file: tuple[Path, str]
|
26
|
+
):
|
42
27
|
"""Verify that the file is accessible from the onsite storage pod using gfal-ls"""
|
43
|
-
|
44
|
-
|
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
|
45
31
|
|
46
|
-
gfal_url = f"{STORAGE_PROTOCOL}://{STORAGE_HOSTNAME}/rucio{test_file_lfn}"
|
32
|
+
gfal_url = f"{STORAGE_PROTOCOL}://{STORAGE_HOSTNAME}/rucio/{test_file_lfn}"
|
47
33
|
cmd = ["gfal-ls", gfal_url]
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
)
|
58
|
-
|
59
|
-
assert any(
|
60
|
-
test_file_name in line for line in stdout.splitlines()
|
61
|
-
), f"File {test_file_name} not accessible; gfal-ls output: {stdout!r}"
|
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
|
62
43
|
|
63
44
|
|
64
45
|
@pytest.mark.usefixtures("_auth_proxy")
|
bdms/tests/utils.py
CHANGED
@@ -13,9 +13,37 @@ from rucio.common.exception import RucioException
|
|
13
13
|
# Default timeout and polling interval (in seconds) for waiting for replication
|
14
14
|
DEFAULT_TIMEOUT = 1000
|
15
15
|
DEFAULT_POLL_INTERVAL = 30
|
16
|
+
XROOTD_UID = int(os.getenv("XROOTD_UID", 994))
|
17
|
+
XROOTD_GID = int(os.getenv("XROOTD_GID", 994))
|
16
18
|
LOGGER = logging.getLogger(__name__)
|
17
19
|
|
18
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
|
+
|
34
|
+
for f in files:
|
35
|
+
# skip temporary files created by rucio
|
36
|
+
# they should already have correct ownership and might go away
|
37
|
+
# between finding them and executing chown
|
38
|
+
if f.endswith(".rucio.upload"):
|
39
|
+
continue
|
40
|
+
|
41
|
+
try:
|
42
|
+
os.chown(root / f, uid, gid)
|
43
|
+
except Exception as e:
|
44
|
+
LOGGER.warning("Failed to chown file %s: %s", root / f, e)
|
45
|
+
|
46
|
+
|
19
47
|
def wait_for_replication_status(
|
20
48
|
rule_client: RuleClient,
|
21
49
|
rule_id: str,
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ctao-bdms-clients
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0rc1
|
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
|
@@ -12,12 +12,18 @@ License-File: LICENSE
|
|
12
12
|
Requires-Dist: astropy<8.0.0a0,>=6.0.1
|
13
13
|
Requires-Dist: ctao-bdms-rucio-policy~=0.1.0
|
14
14
|
Requires-Dist: rucio-clients~=35.7.0
|
15
|
+
Requires-Dist: protozfits>=2.7.2
|
16
|
+
Requires-Dist: watchdog>=6.0.0
|
17
|
+
Requires-Dist: filelock>=3.18.0
|
18
|
+
Requires-Dist: prometheus-client>=0.22.1
|
19
|
+
Requires-Dist: ruamel.yaml
|
15
20
|
Provides-Extra: test
|
16
21
|
Requires-Dist: pytest; extra == "test"
|
17
22
|
Requires-Dist: pytest-cov; extra == "test"
|
18
23
|
Requires-Dist: pytest-requirements; extra == "test"
|
19
24
|
Requires-Dist: python-dotenv; extra == "test"
|
20
25
|
Requires-Dist: minio; extra == "test"
|
26
|
+
Requires-Dist: pytest-xdist; extra == "test"
|
21
27
|
Provides-Extra: doc
|
22
28
|
Requires-Dist: sphinx; extra == "doc"
|
23
29
|
Requires-Dist: numpydoc; extra == "doc"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
bdms/__init__.py,sha256=7btE6tNhFqXSv2eUhZ-0m1J3nTTs4Xo6HWcQI4eh5Do,142
|
2
|
+
bdms/_version.py,sha256=ymwdyKB404aMzKXrx7y01ltePvHS_nIJjfh_zAIIN44,521
|
3
|
+
bdms/acada_ingest_cli.py,sha256=xkf9nT5Lk7SjcbxVeBpKJWuJ-8Luze5-MSq4yki-7_k,12866
|
4
|
+
bdms/acada_ingestion.py,sha256=mB5ilvzJbPblFp94Jcca-IzYvrMuQlroDZxuujpFB_I,36373
|
5
|
+
bdms/extract_fits_metadata.py,sha256=ZGJQCFJCXkWg8N3CAb17GB-wwPj-wTvNg0JOS-MemZ0,3431
|
6
|
+
bdms/version.py,sha256=mTfi1WzbIs991NyImM6mcMg1R39a6U1W2pKnk-Tt5Vw,765
|
7
|
+
bdms/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
bdms/tests/conftest.py,sha256=n7KN9foojCXDxFuZinI0MvhnSvLk5Mn7aFmjQKmO8eI,7364
|
9
|
+
bdms/tests/test_acada_ingest_cli.py,sha256=SYVt1xlEDsrbPX0C5Isf0thjUcaxr7cjflyZSwpPBaw,8314
|
10
|
+
bdms/tests/test_acada_ingestion.py,sha256=xQN07Qbx00IW_w0vCcR5r5H3qvvl_JNYmCUuWJX9xrc,59485
|
11
|
+
bdms/tests/test_basic_rucio_functionality.py,sha256=9GIX8IO6wBJm40LKFEH2StS-fMKvC07sxFHPVR7dftU,3583
|
12
|
+
bdms/tests/test_dpps_rel_0_0.py,sha256=2NhxpdhXQg_8lmK-tRrPQ_FcijsIEfv07x-kVlT8Zik,3138
|
13
|
+
bdms/tests/test_extract_fits_metadata.py,sha256=A935WD2TF3lBcaeDmzGSlH2IXUF1v8qslrsW30lnEAA,3490
|
14
|
+
bdms/tests/test_file_replicas.py,sha256=NqutrSJa5ME50JpmyATNPSLqq1AOq1ruv84XSY3PKLI,2635
|
15
|
+
bdms/tests/test_metadata.py,sha256=f0tSqNGlYe-ydoSDJw0k1De2kHoPl6g-GYBj_jP6kCY,3728
|
16
|
+
bdms/tests/test_onsite_storage.py,sha256=waK7t9kBquzJbuLLYcpeNU9YuA70XTRS88RMxBWxawI,3765
|
17
|
+
bdms/tests/utils.py,sha256=PUayWe60JGVDs5mkWmHVjFV_yqg5XUQlxoAvhT1P0OM,4101
|
18
|
+
ctao_bdms_clients-0.3.0rc1.dist-info/licenses/LICENSE,sha256=Py9riZY_f0CmXbrZ5JreE3WgglyWkRnwUfqydvX6jxE,1556
|
19
|
+
ctao_bdms_clients-0.3.0rc1.dist-info/METADATA,sha256=NRkliF-xYd9V8Vfkdgj2MEFQI9ee67wmUVKxhFD9tMo,2517
|
20
|
+
ctao_bdms_clients-0.3.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
21
|
+
ctao_bdms_clients-0.3.0rc1.dist-info/entry_points.txt,sha256=YZCIOePi_xXaJunA6lAQxAKh1tn3wOd4pmqymFRvah4,60
|
22
|
+
ctao_bdms_clients-0.3.0rc1.dist-info/top_level.txt,sha256=ao0U8aA33KRHpcqmr7yrK8y2AQ6ahSu514tfaN4hDV8,5
|
23
|
+
ctao_bdms_clients-0.3.0rc1.dist-info/RECORD,,
|
@@ -1,18 +0,0 @@
|
|
1
|
-
bdms/__init__.py,sha256=7btE6tNhFqXSv2eUhZ-0m1J3nTTs4Xo6HWcQI4eh5Do,142
|
2
|
-
bdms/_version.py,sha256=6j6NVXRMR-dX2osPsF0-SkvP1-ofWxEz6ew_4VL2kCY,521
|
3
|
-
bdms/acada_ingestion.py,sha256=bKnXbAYvtYHYQk6ir5Sw1YIjCXGZTyk3IpZz-XGkkPo,16248
|
4
|
-
bdms/version.py,sha256=mTfi1WzbIs991NyImM6mcMg1R39a6U1W2pKnk-Tt5Vw,765
|
5
|
-
bdms/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
bdms/tests/conftest.py,sha256=lArkd8Kn7Ef19_BhqXq77taei9LKggWUu3FDUhrt9M4,3009
|
7
|
-
bdms/tests/test_acada_ingestion.py,sha256=A0G9-ssHN3dx0Jz_eIN72dQp21gfZqdQnyAgLY3BDF4,17738
|
8
|
-
bdms/tests/test_basic_rucio_functionality.py,sha256=GFUCq2QlM0M_5k5Qz9iPXPftE6nGuGYbW_IVS76T978,3604
|
9
|
-
bdms/tests/test_dpps_rel_0_0.py,sha256=MnbuBoS_kUUiMcHE3-jqOzekQNUa-wcsjCJqJQ2J9S4,2957
|
10
|
-
bdms/tests/test_file_replicas.py,sha256=NqutrSJa5ME50JpmyATNPSLqq1AOq1ruv84XSY3PKLI,2635
|
11
|
-
bdms/tests/test_metadata.py,sha256=f0tSqNGlYe-ydoSDJw0k1De2kHoPl6g-GYBj_jP6kCY,3728
|
12
|
-
bdms/tests/test_onsite_storage.py,sha256=xBwVbr2q0KHnesIrF0I8ova_hfDXDs3CBya2Sxi6VWM,4633
|
13
|
-
bdms/tests/utils.py,sha256=fh23X6iN2-lsoRBU3WSeWkweiHZlOtIUK5xzHbWyP6c,3185
|
14
|
-
ctao_bdms_clients-0.2.0rc1.dist-info/licenses/LICENSE,sha256=Py9riZY_f0CmXbrZ5JreE3WgglyWkRnwUfqydvX6jxE,1556
|
15
|
-
ctao_bdms_clients-0.2.0rc1.dist-info/METADATA,sha256=88TkbmaMgsbU1dwCRzPHKWK-yYb323BT1HqFgsQboEg,2297
|
16
|
-
ctao_bdms_clients-0.2.0rc1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
17
|
-
ctao_bdms_clients-0.2.0rc1.dist-info/top_level.txt,sha256=ao0U8aA33KRHpcqmr7yrK8y2AQ6ahSu514tfaN4hDV8,5
|
18
|
-
ctao_bdms_clients-0.2.0rc1.dist-info/RECORD,,
|
{ctao_bdms_clients-0.2.0rc1.dist-info → ctao_bdms_clients-0.3.0rc1.dist-info}/licenses/LICENSE
RENAMED
File without changes
|
File without changes
|