diracx-db 0.0.1a44__py3-none-any.whl → 0.0.1a45__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.
- diracx/db/os/job_parameters.py +2 -2
- diracx/db/sql/job/db.py +1 -1
- diracx/db/sql/job_logging/schema.py +1 -1
- diracx/db/sql/sandbox_metadata/db.py +90 -5
- diracx/db/sql/task_queue/db.py +2 -2
- diracx/db/sql/utils/base.py +1 -1
- diracx/db/sql/utils/functions.py +31 -0
- {diracx_db-0.0.1a44.dist-info → diracx_db-0.0.1a45.dist-info}/METADATA +3 -3
- {diracx_db-0.0.1a44.dist-info → diracx_db-0.0.1a45.dist-info}/RECORD +11 -12
- {diracx_db-0.0.1a44.dist-info → diracx_db-0.0.1a45.dist-info}/WHEEL +1 -2
- diracx_db-0.0.1a44.dist-info/top_level.txt +0 -1
- {diracx_db-0.0.1a44.dist-info → diracx_db-0.0.1a45.dist-info}/entry_points.txt +0 -0
diracx/db/os/job_parameters.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
3
|
+
from datetime import UTC, datetime
|
4
4
|
|
5
5
|
from diracx.db.os.utils import BaseOSDB
|
6
6
|
|
@@ -31,7 +31,7 @@ class JobParametersDB(BaseOSDB):
|
|
31
31
|
def upsert(self, vo, doc_id, document):
|
32
32
|
document = {
|
33
33
|
"JobID": doc_id,
|
34
|
-
"timestamp":
|
34
|
+
"timestamp": int(datetime.now(tz=UTC).timestamp() * 1000),
|
35
35
|
**document,
|
36
36
|
}
|
37
37
|
return super().upsert(vo, doc_id, document)
|
diracx/db/sql/job/db.py
CHANGED
@@ -50,7 +50,7 @@ class JobDB(BaseSQLDB):
|
|
50
50
|
}
|
51
51
|
|
52
52
|
# TODO: this is copied from the DIRAC JobDB
|
53
|
-
# but is
|
53
|
+
# but is overwritten in LHCbDIRAC, so we need
|
54
54
|
# to find a way to make it dynamic
|
55
55
|
jdl_2_db_parameters = ["JobName", "JobType", "JobGroup"]
|
56
56
|
|
@@ -44,7 +44,7 @@ class MagicEpochDateTime(TypeDecorator):
|
|
44
44
|
"""
|
45
45
|
if value is None:
|
46
46
|
return None
|
47
|
-
# Carefully convert from Decimal to datetime to avoid
|
47
|
+
# Carefully convert from Decimal to datetime to avoid losing precision
|
48
48
|
value += self.MAGIC_EPOC_NUMBER
|
49
49
|
value_int = int(value)
|
50
50
|
result = datetime.fromtimestamp(value_int, tz=UTC)
|
@@ -1,8 +1,24 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
import logging
|
4
|
+
from contextlib import asynccontextmanager
|
5
|
+
from functools import partial
|
6
|
+
from typing import Any, AsyncGenerator
|
4
7
|
|
5
|
-
from sqlalchemy import
|
8
|
+
from sqlalchemy import (
|
9
|
+
BigInteger,
|
10
|
+
Column,
|
11
|
+
Executable,
|
12
|
+
MetaData,
|
13
|
+
Table,
|
14
|
+
and_,
|
15
|
+
delete,
|
16
|
+
insert,
|
17
|
+
literal,
|
18
|
+
or_,
|
19
|
+
select,
|
20
|
+
update,
|
21
|
+
)
|
6
22
|
from sqlalchemy.exc import IntegrityError, NoResultFound
|
7
23
|
|
8
24
|
from diracx.core.exceptions import (
|
@@ -12,15 +28,25 @@ from diracx.core.exceptions import (
|
|
12
28
|
)
|
13
29
|
from diracx.core.models import SandboxInfo, SandboxType, UserInfo
|
14
30
|
from diracx.db.sql.utils.base import BaseSQLDB
|
15
|
-
from diracx.db.sql.utils.functions import utcnow
|
31
|
+
from diracx.db.sql.utils.functions import days_since, utcnow
|
16
32
|
|
17
33
|
from .schema import Base as SandboxMetadataDBBase
|
18
34
|
from .schema import SandBoxes, SBEntityMapping, SBOwners
|
19
35
|
|
36
|
+
logger = logging.getLogger(__name__)
|
37
|
+
|
20
38
|
|
21
39
|
class SandboxMetadataDB(BaseSQLDB):
|
22
40
|
metadata = SandboxMetadataDBBase.metadata
|
23
41
|
|
42
|
+
# Temporary table to store the sandboxes to delete, see `select_and_delete_expired`
|
43
|
+
_temp_table = Table(
|
44
|
+
"sb_to_delete",
|
45
|
+
MetaData(),
|
46
|
+
Column("SBId", BigInteger, primary_key=True),
|
47
|
+
prefixes=["TEMPORARY"],
|
48
|
+
)
|
49
|
+
|
24
50
|
async def get_owner_id(self, user: UserInfo) -> int | None:
|
25
51
|
"""Get the id of the owner from the database."""
|
26
52
|
stmt = select(SBOwners.OwnerID).where(
|
@@ -85,7 +111,13 @@ class SandboxMetadataDB(BaseSQLDB):
|
|
85
111
|
.values(LastAccessTime=utcnow())
|
86
112
|
)
|
87
113
|
result = await self.conn.execute(stmt)
|
88
|
-
|
114
|
+
if result.rowcount == 0:
|
115
|
+
# If the update didn't affect any row, the sandbox doesn't exist
|
116
|
+
raise SandboxNotFoundError(pfn, se_name)
|
117
|
+
elif result.rowcount != 1:
|
118
|
+
raise NotImplementedError(
|
119
|
+
"More than one sandbox was updated. This should not happen."
|
120
|
+
)
|
89
121
|
|
90
122
|
async def sandbox_is_assigned(self, pfn: str, se_name: str) -> bool | None:
|
91
123
|
"""Checks if a sandbox exists and has been assigned."""
|
@@ -128,7 +160,7 @@ class SandboxMetadataDB(BaseSQLDB):
|
|
128
160
|
sb_type: SandboxType,
|
129
161
|
se_name: str,
|
130
162
|
) -> None:
|
131
|
-
"""
|
163
|
+
"""Map sandbox and jobs."""
|
132
164
|
for job_id in jobs_ids:
|
133
165
|
# Define the entity id as 'Entity:entity_id' due to the DB definition:
|
134
166
|
entity_id = self.jobid_to_entity_id(job_id)
|
@@ -187,3 +219,56 @@ class SandboxMetadataDB(BaseSQLDB):
|
|
187
219
|
.values(Assigned=False)
|
188
220
|
)
|
189
221
|
await self.conn.execute(unassign_stmt)
|
222
|
+
|
223
|
+
@asynccontextmanager
|
224
|
+
async def delete_unused_sandboxes(
|
225
|
+
self, *, limit: int | None = None
|
226
|
+
) -> AsyncGenerator[AsyncGenerator[str, None], None]:
|
227
|
+
"""Get the sandbox PFNs to delete.
|
228
|
+
|
229
|
+
The result of this function can be used as an async context manager
|
230
|
+
to yield the PFNs of the sandboxes to delete. The context manager
|
231
|
+
will automatically remove the sandboxes from the database upon exit.
|
232
|
+
|
233
|
+
Args:
|
234
|
+
limit: If not None, the maximum number of sandboxes to delete.
|
235
|
+
|
236
|
+
"""
|
237
|
+
conditions = [
|
238
|
+
# If it has assigned to a job but is no longer mapped it can be removed
|
239
|
+
# and_(SandBoxes.Assigned, ~exists(SandBoxes.SBId == SBEntityMapping.SBId)),
|
240
|
+
# If the sandbox is still unassigned after 15 days, remove it
|
241
|
+
and_(~SandBoxes.Assigned, days_since(SandBoxes.LastAccessTime) >= 15),
|
242
|
+
]
|
243
|
+
# Sandboxes which are not on S3 will be handled by legacy DIRAC
|
244
|
+
condition = and_(SandBoxes.SEPFN.like("/S3/%"), or_(*conditions))
|
245
|
+
|
246
|
+
# Copy the in-flight rows to a temporary table
|
247
|
+
await self.conn.run_sync(partial(self._temp_table.create, checkfirst=True))
|
248
|
+
select_stmt = select(SandBoxes.SBId).where(condition)
|
249
|
+
if limit:
|
250
|
+
select_stmt = select_stmt.limit(limit)
|
251
|
+
insert_stmt = insert(self._temp_table).from_select(["SBId"], select_stmt)
|
252
|
+
await self.conn.execute(insert_stmt)
|
253
|
+
|
254
|
+
try:
|
255
|
+
# Select the sandbox PFNs from the temporary table and yield them
|
256
|
+
select_stmt = select(SandBoxes.SEPFN).join(
|
257
|
+
self._temp_table, self._temp_table.c.SBId == SandBoxes.SBId
|
258
|
+
)
|
259
|
+
|
260
|
+
async def yield_pfns() -> AsyncGenerator[str, None]:
|
261
|
+
async for row in await self.conn.stream(select_stmt):
|
262
|
+
yield row.SEPFN
|
263
|
+
|
264
|
+
yield yield_pfns()
|
265
|
+
|
266
|
+
# Delete the sandboxes from the main table
|
267
|
+
delete_stmt = delete(SandBoxes).where(
|
268
|
+
SandBoxes.SBId.in_(select(self._temp_table.c.SBId))
|
269
|
+
)
|
270
|
+
result = await self.conn.execute(delete_stmt)
|
271
|
+
logger.info("Deleted %d expired/unassigned sandboxes", result.rowcount)
|
272
|
+
|
273
|
+
finally:
|
274
|
+
await self.conn.run_sync(partial(self._temp_table.drop, checkfirst=True))
|
diracx/db/sql/task_queue/db.py
CHANGED
@@ -56,7 +56,7 @@ class TaskQueueDB(BaseSQLDB):
|
|
56
56
|
)
|
57
57
|
rows = await self.conn.execute(stmt)
|
58
58
|
# Get owners in this group and the amount of times they appear
|
59
|
-
# TODO: I guess the rows are already a list of
|
59
|
+
# TODO: I guess the rows are already a list of tuples
|
60
60
|
# maybe refactor
|
61
61
|
return {r[0]: r[1] for r in rows if r}
|
62
62
|
|
@@ -108,7 +108,7 @@ class TaskQueueDB(BaseSQLDB):
|
|
108
108
|
tq_ids: list[int],
|
109
109
|
priority: float,
|
110
110
|
):
|
111
|
-
"""Set the priority for a user/userGroup combo given a
|
111
|
+
"""Set the priority for a user/userGroup combo given a split share."""
|
112
112
|
update_stmt = (
|
113
113
|
update(TaskQueues)
|
114
114
|
.where(TaskQueues.TQId.in_(tq_ids))
|
diracx/db/sql/utils/base.py
CHANGED
@@ -51,7 +51,7 @@ class BaseSQLDB(metaclass=ABCMeta):
|
|
51
51
|
of the same database. To list the available implementations use
|
52
52
|
`BaseSQLDB.available_implementations(db_name)`. The first entry in this list
|
53
53
|
will be the preferred implementation and it can be initialized by calling
|
54
|
-
it's `__init__` function with a URL
|
54
|
+
it's `__init__` function with a URL previously obtained from
|
55
55
|
`BaseSQLDB.available_urls`.
|
56
56
|
|
57
57
|
To control the lifetime of the SQLAlchemy engine used for connecting to the
|
diracx/db/sql/utils/functions.py
CHANGED
@@ -103,6 +103,37 @@ def sqlite_date_trunc(element, compiler, **kw):
|
|
103
103
|
)
|
104
104
|
|
105
105
|
|
106
|
+
class days_since(expression.FunctionElement): # noqa: N801
|
107
|
+
"""Sqlalchemy function to get the number of days since a given date.
|
108
|
+
|
109
|
+
Primarily used to be able to query for a specific resolution of a date e.g.
|
110
|
+
|
111
|
+
select * from table where days_since(date_column) = 0
|
112
|
+
select * from table where days_since(date_column) = 1
|
113
|
+
"""
|
114
|
+
|
115
|
+
type = DateTime()
|
116
|
+
inherit_cache = False
|
117
|
+
|
118
|
+
def __init__(self, *args, **kwargs) -> None:
|
119
|
+
super().__init__(*args, **kwargs)
|
120
|
+
|
121
|
+
|
122
|
+
@compiles(days_since, "postgresql")
|
123
|
+
def pg_days_since(element, compiler, **kw):
|
124
|
+
return f"EXTRACT(DAY FROM (now() - {compiler.process(element.clauses)}))"
|
125
|
+
|
126
|
+
|
127
|
+
@compiles(days_since, "mysql")
|
128
|
+
def mysql_days_since(element, compiler, **kw):
|
129
|
+
return f"DATEDIFF(NOW(), {compiler.process(element.clauses)})"
|
130
|
+
|
131
|
+
|
132
|
+
@compiles(days_since, "sqlite")
|
133
|
+
def sqlite_days_since(element, compiler, **kw):
|
134
|
+
return f"julianday('now') - julianday({compiler.process(element.clauses)})"
|
135
|
+
|
136
|
+
|
106
137
|
def substract_date(**kwargs: float) -> datetime:
|
107
138
|
return datetime.now(tz=timezone.utc) - timedelta(**kwargs)
|
108
139
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: diracx-db
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.1a45
|
4
4
|
Summary: TODO
|
5
5
|
License: GPL-3.0-only
|
6
6
|
Classifier: Intended Audience :: Science/Research
|
@@ -9,11 +9,11 @@ Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Topic :: Scientific/Engineering
|
10
10
|
Classifier: Topic :: System :: Distributed Computing
|
11
11
|
Requires-Python: >=3.11
|
12
|
-
Description-Content-Type: text/markdown
|
13
12
|
Requires-Dist: diracx-core
|
14
13
|
Requires-Dist: opensearch-py[async]
|
15
14
|
Requires-Dist: pydantic>=2.10
|
16
15
|
Requires-Dist: sqlalchemy[aiomysql,aiosqlite]>=2
|
17
16
|
Requires-Dist: uuid-utils
|
18
17
|
Provides-Extra: testing
|
19
|
-
Requires-Dist: diracx-testing; extra ==
|
18
|
+
Requires-Dist: diracx-testing; extra == 'testing'
|
19
|
+
Requires-Dist: freezegun; extra == 'testing'
|
@@ -3,7 +3,7 @@ diracx/db/__main__.py,sha256=tU4tp3OAClYCiPMxlRj524sZGBx9oy4CoWHd8pMuEEs,1715
|
|
3
3
|
diracx/db/exceptions.py,sha256=1nn-SZLG-nQwkxbvHjZqXhE5ouzWj1f3qhSda2B4ZEg,83
|
4
4
|
diracx/db/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
5
|
diracx/db/os/__init__.py,sha256=IZr6z6SefrRvuC8sTC4RmB3_wwOyEt1GzpDuwSMH8O4,112
|
6
|
-
diracx/db/os/job_parameters.py,sha256=
|
6
|
+
diracx/db/os/job_parameters.py,sha256=loAc-bo3u-RMAp_H1g8VRt8T-rCCsXp_d9aCvg5OS-A,1225
|
7
7
|
diracx/db/os/utils.py,sha256=V4T-taos64SFNcorfIr7mq5l5y88K6TzyCj1YqWk8VI,11562
|
8
8
|
diracx/db/sql/__init__.py,sha256=JYu0b0IVhoXy3lX2m2r2dmAjsRS7IbECBUMEDvX0Te4,391
|
9
9
|
diracx/db/sql/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -13,26 +13,25 @@ diracx/db/sql/dummy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
13
13
|
diracx/db/sql/dummy/db.py,sha256=IW4FzG7ERKbhZvC32KL7Rodu2u-zKAf8BryO4VAdJew,1650
|
14
14
|
diracx/db/sql/dummy/schema.py,sha256=9zI53pKlzc6qBezsyjkatOQrNZdGCjwgjQ8Iz_pyAXs,789
|
15
15
|
diracx/db/sql/job/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
-
diracx/db/sql/job/db.py,sha256=
|
16
|
+
diracx/db/sql/job/db.py,sha256=TnEc0fckiuMJAZg2v1_Pbwfn7kDPDam6TXp9ySuiddk,11910
|
17
17
|
diracx/db/sql/job/schema.py,sha256=eFgZshe6NEzOM2qI0HI9Y3abrqDMoQIwa9L0vZugHcU,5431
|
18
18
|
diracx/db/sql/job_logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
19
|
diracx/db/sql/job_logging/db.py,sha256=BYzlPuvdvHR7wdzQEVWMH_V5kL0bLBZtQkcugnSGbjs,5497
|
20
|
-
diracx/db/sql/job_logging/schema.py,sha256=
|
20
|
+
diracx/db/sql/job_logging/schema.py,sha256=k6uBw-RHAcJ5GEleNpiWoXEJBhCiNG-y4xAgBKHZjjM,2524
|
21
21
|
diracx/db/sql/pilot_agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
diracx/db/sql/pilot_agents/db.py,sha256=7-cuCbh_KhM0jlybsHMWV-W66bHsPHIVBpbuqwjncj0,1232
|
23
23
|
diracx/db/sql/pilot_agents/schema.py,sha256=KeWnFSpYOTrT3-_rOCFjbjNnPNXKnUZiJVsu4vv5U2U,2149
|
24
24
|
diracx/db/sql/sandbox_metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
|
-
diracx/db/sql/sandbox_metadata/db.py,sha256=
|
25
|
+
diracx/db/sql/sandbox_metadata/db.py,sha256=DAmuk-PDGKq3eLV2EovDGnJI0GDiH5u8C74ARIy8MWo,10171
|
26
26
|
diracx/db/sql/sandbox_metadata/schema.py,sha256=V5gV2PHwzTbBz_th9ribLfE7Lqk8YGemDmvqq4jWQJ4,1530
|
27
27
|
diracx/db/sql/task_queue/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
|
-
diracx/db/sql/task_queue/db.py,sha256=
|
28
|
+
diracx/db/sql/task_queue/db.py,sha256=2qul1D2tX2uCI92N591WK5xWHakG0pNibzDwKQ7W-I8,6246
|
29
29
|
diracx/db/sql/task_queue/schema.py,sha256=5efAgvNYRkLlaJ2NzRInRfmVa3tyIzQu2l0oRPy4Kzw,3258
|
30
30
|
diracx/db/sql/utils/__init__.py,sha256=QkvpqBuIAgkAOywAssYzdxSzUQVZlSUumK7mPxotXfM,547
|
31
|
-
diracx/db/sql/utils/base.py,sha256=
|
32
|
-
diracx/db/sql/utils/functions.py,sha256=
|
31
|
+
diracx/db/sql/utils/base.py,sha256=HYQuX16mgg9LAMtAEmbTmJFIN0OSMe1Hcb57dtl7LCc,12367
|
32
|
+
diracx/db/sql/utils/functions.py,sha256=_E4tc9Gti6LuSh7QEyoqPJSvCuByVqvRenOXCzxsulE,4014
|
33
33
|
diracx/db/sql/utils/types.py,sha256=yU-tXsu6hFGPsr9ba1n3ZjGPnHQI_06lbpkTeDCWJtg,1287
|
34
|
-
diracx_db-0.0.
|
35
|
-
diracx_db-0.0.
|
36
|
-
diracx_db-0.0.
|
37
|
-
diracx_db-0.0.
|
38
|
-
diracx_db-0.0.1a44.dist-info/RECORD,,
|
34
|
+
diracx_db-0.0.1a45.dist-info/METADATA,sha256=ET2Uo-DegfUc6qRqvicSGR7PpWTtc4yXJKqoZQRbXyQ,675
|
35
|
+
diracx_db-0.0.1a45.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
36
|
+
diracx_db-0.0.1a45.dist-info/entry_points.txt,sha256=UPqhLvb9gui0kOyWeI_edtefcrHToZmQt1p76vIwujo,317
|
37
|
+
diracx_db-0.0.1a45.dist-info/RECORD,,
|
@@ -1 +0,0 @@
|
|
1
|
-
diracx
|
File without changes
|