Skip to content

Commit f2e8c46

Browse files
committed
refactor: restructure to follow n-layer architecture
1 parent a927994 commit f2e8c46

7 files changed

Lines changed: 135 additions & 148 deletions

File tree

diracx-core/src/diracx/core/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ def __init__(self, pfn: str, se_name: str, detail: str | None = None):
7070
)
7171

7272

73+
class ResourceNotFoundError(DiracError):
74+
def __init__(self, name: str, detail: str | None = None):
75+
self.name: str = name
76+
super().__init__(f"{name} not found" + (f" ({detail})" if detail else ""))
77+
78+
7379
class SandboxAlreadyAssignedError(DiracError):
7480
def __init__(self, pfn: str, se_name: str, detail: str | None = None):
7581
self.pfn: str = pfn

diracx-core/tests/test_rss.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from diracx.core.models.rss import (
6+
AllowedStatus,
7+
BannedStatus,
8+
map_status,
9+
)
10+
11+
12+
@pytest.mark.parametrize("status", ["Active", "Degraded"])
13+
async def test_map_status_allowed(status):
14+
assert bool(map_status(status, "")) is True
15+
assert isinstance(map_status(status, ""), AllowedStatus)
16+
17+
18+
@pytest.mark.parametrize("status", ["Banned", "Probing", "Error", "Unknown"])
19+
async def test_map_status_banned(status):
20+
result = map_status(status, "CE banned")
21+
assert bool(result) is False
22+
assert isinstance(result, BannedStatus)
23+
assert result.reason == "CE banned"
24+
25+
26+
async def test_map_status_unknown_banned():
27+
result = map_status("WeirdValue", "")
28+
assert bool(result) is False
29+
assert isinstance(result, BannedStatus)
30+
assert result.reason == "Unknown status: WeirdValue"

diracx-db/src/diracx/db/sql/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"JobDB",
66
"JobLoggingDB",
77
"PilotAgentsDB",
8+
"ResourceStatusDB",
89
"SandboxMetadataDB",
910
"TaskQueueDB",
1011
)
@@ -13,5 +14,6 @@
1314
from .job.db import JobDB
1415
from .job_logging.db import JobLoggingDB
1516
from .pilot_agents.db import PilotAgentsDB
17+
from .rss.db import ResourceStatusDB
1618
from .sandbox_metadata.db import SandboxMetadataDB
1719
from .task_queue.db import TaskQueueDB
Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
11
from __future__ import annotations
22

33
from sqlalchemy import select
4+
from sqlalchemy.engine import Row
45

5-
from diracx.core.models.rss import (
6-
ComputeElementStatus,
7-
FTSStatus,
8-
ResourceType,
9-
StorageElementStatus,
10-
map_status,
11-
)
12-
from diracx.core.models.rss import (
13-
SiteStatus as SiteStatusModel,
14-
)
6+
from diracx.core.exceptions import ResourceNotFoundError
157

168
from ..utils import BaseSQLDB
179
from .schema import (
@@ -26,67 +18,35 @@ class ResourceStatusDB(BaseSQLDB):
2618

2719
metadata = RSSBase.metadata
2820

29-
async def get_site_status(self, name: str, vo: str = "all") -> SiteStatusModel:
21+
async def get_site_status(self, name: str, vo: str = "all") -> tuple[str, str]:
3022
stmt = select(SiteStatus.status, SiteStatus.reason).where(
3123
SiteStatus.name == name,
3224
SiteStatus.statustype == "all",
3325
SiteStatus.vo == vo,
3426
)
3527
result = await self.conn.execute(stmt)
36-
row = result.first()
28+
row = result.one_or_none()
3729
if not row:
38-
raise ValueError(f"Site {name} not found")
30+
raise ResourceNotFoundError(name)
3931

40-
return SiteStatusModel(all=map_status(row.Status, row.Reason))
32+
return row.Status, row.Reason
4133

4234
async def get_resource_status(
4335
self,
4436
name: str,
37+
statustypes: list[str] = ["all"],
4538
vo: str = "all",
46-
) -> ComputeElementStatus | FTSStatus:
39+
) -> dict[str, Row]:
4740
stmt = select(
48-
ResourceStatus.status, ResourceStatus.reason, ResourceStatus.elementtype
41+
ResourceStatus.status, ResourceStatus.reason, ResourceStatus.statustype
4942
).where(
5043
ResourceStatus.name == name,
51-
ResourceStatus.statustype == "all",
44+
ResourceStatus.statustype.in_(statustypes),
5245
ResourceStatus.vo == vo,
5346
)
5447
result = await self.conn.execute(stmt)
55-
row = result.first()
56-
57-
if not row:
58-
raise ValueError(f"Resource {name} not found")
59-
60-
element_type = ResourceType(row.ElementType)
61-
62-
if element_type == ResourceType.Compute:
63-
return ComputeElementStatus(all=map_status(row.Status, row.Reason))
64-
if element_type == ResourceType.FTS:
65-
return FTSStatus(all=map_status(row.Status, row.Reason))
48+
rows = result.all()
6649

67-
raise ValueError(f"Unexpected resource type {element_type}")
68-
69-
async def get_storage_status(
70-
self, name: str, vo: str = "all"
71-
) -> StorageElementStatus:
72-
async def get_status(statustype: str):
73-
stmt = select(ResourceStatus.status, ResourceStatus.reason).where(
74-
ResourceStatus.name == name,
75-
ResourceStatus.statustype == statustype,
76-
ResourceStatus.vo == vo,
77-
)
78-
79-
result = await self.conn.execute(stmt)
80-
row = result.first()
81-
82-
if not row:
83-
raise ValueError(f"StorageElement {name} not found")
84-
85-
return map_status(row.Status, row.Reason)
86-
87-
return StorageElementStatus(
88-
read=await get_status("ReadAccess"),
89-
write=await get_status("WriteAccess"),
90-
check=await get_status("CheckAccess"),
91-
remove=await get_status("RemoveAccess"),
92-
)
50+
if not rows:
51+
raise ResourceNotFoundError(name)
52+
return {row.StatusType: row for row in rows}

diracx-db/tests/rss/test_rss_db.py

Lines changed: 36 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
1-
# diracx-db/tests/sql/rss/test_rss.py
21
from __future__ import annotations
32

43
from datetime import datetime, timezone
54

65
import pytest
76
from sqlalchemy import insert
87

9-
from diracx.core.models.rss import (
10-
AllowedStatus,
11-
BannedStatus,
12-
ComputeElementStatus,
13-
FTSStatus,
14-
StorageElementStatus,
15-
map_status,
16-
)
17-
from diracx.core.models.rss import SiteStatus as SiteStatusModel
8+
from diracx.core.exceptions import ResourceNotFoundError
189
from diracx.db.sql.rss.db import ResourceStatusDB
1910

2011
_NOW = datetime(2024, 1, 1, tzinfo=timezone.utc)
@@ -30,27 +21,6 @@ async def rss_db(tmp_path):
3021
yield rss_db
3122

3223

33-
@pytest.mark.parametrize("status", ["Active", "Degraded"])
34-
async def test_map_status_allowed(status):
35-
assert bool(map_status(status, "")) is True
36-
assert isinstance(map_status(status, ""), AllowedStatus)
37-
38-
39-
@pytest.mark.parametrize("status", ["Banned", "Probing", "Error", "Unknown"])
40-
async def test_map_status_banned(status):
41-
result = map_status(status, "CE banned")
42-
assert bool(result) is False
43-
assert isinstance(result, BannedStatus)
44-
assert result.reason == "CE banned"
45-
46-
47-
async def test_map_status_unknown_banned():
48-
result = map_status("WeirdValue", "")
49-
assert bool(result) is False
50-
assert isinstance(result, BannedStatus)
51-
assert result.reason == "Unknown status: WeirdValue"
52-
53-
5424
async def test_site_status(rss_db: ResourceStatusDB):
5525
# Insert a test Site
5626
async with rss_db.engine.begin() as conn:
@@ -71,13 +41,12 @@ async def test_site_status(rss_db: ResourceStatusDB):
7141

7242
# Test with the test Site (should be found)
7343
async with rss_db as rss_db:
74-
result = await rss_db.get_site_status("TestSite")
75-
assert isinstance(result, SiteStatusModel)
76-
assert isinstance(result.all, AllowedStatus)
77-
assert bool(result.all) is True
44+
status, reason = await rss_db.get_site_status("TestSite")
45+
assert status == "Active"
46+
assert reason == "All good"
7847

7948
# Test with an unknow Site (should not be found)
80-
with pytest.raises(ValueError, match="Site Unknown not found"):
49+
with pytest.raises(ResourceNotFoundError):
8150
async with rss_db as rss_db:
8251
await rss_db.get_site_status("Unknown")
8352

@@ -114,50 +83,7 @@ async def test_resource_status(rss_db: ResourceStatusDB):
11483
TokenOwner="test",
11584
)
11685
)
117-
# Insert a wrong test
118-
await conn.execute(
119-
insert(rss_db.metadata.tables["ResourceStatus"]).values(
120-
Name="WrongTest",
121-
StatusType="all",
122-
VO="all",
123-
Status="Active",
124-
Reason="All good",
125-
DateEffective=_NOW,
126-
TokenExpiration=_FAR,
127-
LastCheckTime=_NOW,
128-
ElementType="WrongTest",
129-
TokenOwner="WrongTest",
130-
)
131-
)
132-
133-
# Test with the test Compute Element (should be found)
134-
async with rss_db as rss_db:
135-
result = await rss_db.get_resource_status("TestCompute")
136-
assert isinstance(result, ComputeElementStatus)
137-
assert isinstance(result.all, AllowedStatus)
138-
assert bool(result.all) is True
139-
140-
# Test with the test FTS (should be found)
141-
async with rss_db as rss_db:
142-
result = await rss_db.get_resource_status("TestFTS")
143-
assert isinstance(result, FTSStatus)
144-
assert isinstance(result.all, AllowedStatus)
145-
assert bool(result.all) is True
146-
147-
# Test with a wrong Resource type
148-
with pytest.raises(ValueError, match="not a valid ResourceType"):
149-
async with rss_db as rss_db:
150-
await rss_db.get_resource_status("WrongTest")
151-
152-
# Test with an unknow Resource (should not be found)
153-
with pytest.raises(ValueError, match="Resource Unknown not found"):
154-
async with rss_db as rss_db:
155-
await rss_db.get_resource_status("Unknown")
156-
157-
158-
async def test_storage_status(rss_db: ResourceStatusDB):
159-
# Insert a test Storage Element with all StatusType
160-
async with rss_db.engine.begin() as conn:
86+
# Insert a test Storage Element with all StatusType
16187
for statustype in ["ReadAccess", "WriteAccess", "CheckAccess", "RemoveAccess"]:
16288
await conn.execute(
16389
insert(rss_db.metadata.tables["ResourceStatus"]).values(
@@ -174,20 +100,36 @@ async def test_storage_status(rss_db: ResourceStatusDB):
174100
)
175101
)
176102

103+
# Test with the test Compute Element (should be found)
104+
async with rss_db as rss_db:
105+
result = await rss_db.get_resource_status("TestCompute")
106+
assert "all" in result
107+
assert result["all"].Status == "Active"
108+
assert result["all"].Reason == "All good"
109+
110+
# Test with the test FTS (should be found)
111+
async with rss_db as rss_db:
112+
result = await rss_db.get_resource_status("TestFTS")
113+
assert "all" in result
114+
assert result["all"].Status == "Active"
115+
assert result["all"].Reason == "All good"
116+
177117
# Test with the test Storage Element (should be found)
178118
async with rss_db as rss_db:
179-
result = await rss_db.get_storage_status("TestStorage")
180-
assert isinstance(result, StorageElementStatus)
181-
assert isinstance(result.read, AllowedStatus)
182-
assert isinstance(result.write, AllowedStatus)
183-
assert isinstance(result.check, AllowedStatus)
184-
assert isinstance(result.remove, AllowedStatus)
185-
assert bool(result.read) is True
186-
assert bool(result.write) is True
187-
assert bool(result.check) is True
188-
assert bool(result.remove) is True
189-
190-
# Test with an unknow Storage Element (should not be found)
191-
with pytest.raises(ValueError, match="StorageElement Unknown not found"):
119+
result = await rss_db.get_resource_status(
120+
"TestStorage", ["ReadAccess", "WriteAccess", "CheckAccess", "RemoveAccess"]
121+
)
122+
assert set(result.keys()) == {
123+
"ReadAccess",
124+
"WriteAccess",
125+
"CheckAccess",
126+
"RemoveAccess",
127+
}
128+
for row in result.values():
129+
assert row.Status == "Active"
130+
assert row.Reason == "All good"
131+
132+
# Test with an unknow Resource (should not be found)
133+
with pytest.raises(ResourceNotFoundError):
192134
async with rss_db as rss_db:
193-
await rss_db.get_storage_status("Unknown")
135+
await rss_db.get_resource_status("Unknown")

diracx-logic/src/diracx/logic/rss/__init__.py

Whitespace-only changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from __future__ import annotations
2+
3+
from diracx.core.models.rss import (
4+
ComputeElementStatus,
5+
FTSStatus,
6+
StorageElementStatus,
7+
map_status,
8+
)
9+
from diracx.core.models.rss import (
10+
SiteStatus as SiteStatusModel,
11+
)
12+
from diracx.db.sql import ResourceStatusDB
13+
14+
15+
async def get_site_status(
16+
resource_status_db: ResourceStatusDB, name: str, vo: str
17+
) -> SiteStatusModel:
18+
status, reason = await resource_status_db.get_site_status(name, vo)
19+
return SiteStatusModel(all=map_status(status, reason))
20+
21+
22+
async def get_compute_status(
23+
resource_status_db: ResourceStatusDB, name: str, vo: str
24+
) -> ComputeElementStatus:
25+
rows = await resource_status_db.get_resource_status(name, ["all"], vo)
26+
return ComputeElementStatus(all=map_status(rows["all"].Status, rows["all"].Reason))
27+
28+
29+
async def get_fts_status(
30+
resource_status_db: ResourceStatusDB, name: str, vo: str
31+
) -> FTSStatus:
32+
rows = await resource_status_db.get_resource_status(name, ["all"], vo)
33+
return FTSStatus(all=map_status(rows["all"].Status, rows["all"].Reason))
34+
35+
36+
async def get_storage_status(
37+
resource_status_db: ResourceStatusDB, name: str, vo: str
38+
) -> StorageElementStatus:
39+
rows = await resource_status_db.get_resource_status(
40+
name, ["ReadAccess", "WriteAccess", "CheckAccess", "RemoveAccess"], vo
41+
)
42+
return StorageElementStatus(
43+
read=map_status(rows["ReadAccess"].Status, rows["ReadAccess"].Reason),
44+
write=map_status(rows["WriteAccess"].Status, rows["WriteAccess"].Reason),
45+
check=map_status(rows["CheckAccess"].Status, rows["CheckAccess"].Reason),
46+
remove=map_status(rows["RemoveAccess"].Status, rows["RemoveAccess"].Reason),
47+
)

0 commit comments

Comments
 (0)