diracx-db 0.0.1a33__py3-none-any.whl → 0.0.1a35__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 +15 -4
- diracx/db/os/utils.py +10 -5
- diracx/db/sql/job/db.py +94 -7
- diracx/db/sql/job/schema.py +32 -5
- {diracx_db-0.0.1a33.dist-info → diracx_db-0.0.1a35.dist-info}/METADATA +1 -1
- {diracx_db-0.0.1a33.dist-info → diracx_db-0.0.1a35.dist-info}/RECORD +9 -9
- {diracx_db-0.0.1a33.dist-info → diracx_db-0.0.1a35.dist-info}/WHEEL +0 -0
- {diracx_db-0.0.1a33.dist-info → diracx_db-0.0.1a35.dist-info}/entry_points.txt +0 -0
- {diracx_db-0.0.1a33.dist-info → diracx_db-0.0.1a35.dist-info}/top_level.txt +0 -0
diracx/db/os/job_parameters.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from DIRAC.Core.Utilities import TimeUtilities
|
4
|
+
|
3
5
|
from diracx.db.os.utils import BaseOSDB
|
4
6
|
|
5
7
|
|
@@ -18,8 +20,17 @@ class JobParametersDB(BaseOSDB):
|
|
18
20
|
"Status": {"type": "keyword"},
|
19
21
|
"JobType": {"type": "keyword"},
|
20
22
|
}
|
21
|
-
|
23
|
+
# TODO: Does this need to be configurable?
|
24
|
+
index_prefix = "job_parameters"
|
25
|
+
|
26
|
+
def index_name(self, vo, doc_id: int) -> str:
|
27
|
+
split = int(int(doc_id) // 1e6)
|
28
|
+
return f"{self.index_prefix}_{vo}_{split}m"
|
22
29
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
30
|
+
def upsert(self, vo, doc_id, document):
|
31
|
+
document = {
|
32
|
+
"JobID": doc_id,
|
33
|
+
"timestamp": TimeUtilities.toEpochMilliSeconds(),
|
34
|
+
**document,
|
35
|
+
}
|
36
|
+
return super().upsert(vo, doc_id, document)
|
diracx/db/os/utils.py
CHANGED
@@ -77,7 +77,7 @@ class BaseOSDB(metaclass=ABCMeta):
|
|
77
77
|
index_prefix: str
|
78
78
|
|
79
79
|
@abstractmethod
|
80
|
-
def index_name(self, doc_id: int) -> str: ...
|
80
|
+
def index_name(self, vo: str, doc_id: int) -> str: ...
|
81
81
|
|
82
82
|
def __init__(self, connection_kwargs: dict[str, Any]) -> None:
|
83
83
|
self._client: AsyncOpenSearch | None = None
|
@@ -182,15 +182,20 @@ class BaseOSDB(metaclass=ABCMeta):
|
|
182
182
|
)
|
183
183
|
assert result["acknowledged"]
|
184
184
|
|
185
|
-
async def upsert(self, doc_id, document) -> None:
|
186
|
-
|
185
|
+
async def upsert(self, vo: str, doc_id: int, document: Any) -> None:
|
186
|
+
index_name = self.index_name(vo, doc_id)
|
187
187
|
response = await self.client.update(
|
188
|
-
index=
|
188
|
+
index=index_name,
|
189
189
|
id=doc_id,
|
190
190
|
body={"doc": document, "doc_as_upsert": True},
|
191
191
|
params=dict(retry_on_conflict=10),
|
192
192
|
)
|
193
|
-
|
193
|
+
logger.debug(
|
194
|
+
"Upserted document %s in index %s with response: %s",
|
195
|
+
doc_id,
|
196
|
+
index_name,
|
197
|
+
response,
|
198
|
+
)
|
194
199
|
|
195
200
|
async def search(
|
196
201
|
self, parameters, search, sorts, *, per_page: int = 100, page: int | None = None
|
diracx/db/sql/job/db.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
__all__ = ["JobDB"]
|
4
|
+
|
3
5
|
from datetime import datetime, timezone
|
4
|
-
from typing import TYPE_CHECKING, Any
|
6
|
+
from typing import TYPE_CHECKING, Any, Iterable
|
5
7
|
|
6
8
|
from sqlalchemy import bindparam, case, delete, func, insert, select, update
|
7
9
|
|
@@ -9,13 +11,12 @@ if TYPE_CHECKING:
|
|
9
11
|
from sqlalchemy.sql.elements import BindParameter
|
10
12
|
|
11
13
|
from diracx.core.exceptions import InvalidQueryError
|
12
|
-
from diracx.core.models import
|
13
|
-
SearchSpec,
|
14
|
-
SortSpec,
|
15
|
-
)
|
14
|
+
from diracx.core.models import JobCommand, SearchSpec, SortSpec
|
16
15
|
|
17
16
|
from ..utils import BaseSQLDB, apply_search_filters, apply_sort_constraints
|
17
|
+
from ..utils.functions import utcnow
|
18
18
|
from .schema import (
|
19
|
+
HeartBeatLoggingInfo,
|
19
20
|
InputData,
|
20
21
|
JobCommands,
|
21
22
|
JobDBBase,
|
@@ -38,6 +39,16 @@ def _get_columns(table, parameters):
|
|
38
39
|
class JobDB(BaseSQLDB):
|
39
40
|
metadata = JobDBBase.metadata
|
40
41
|
|
42
|
+
# Field names which should be stored in the HeartBeatLoggingInfo table
|
43
|
+
heartbeat_fields = {
|
44
|
+
"LoadAverage",
|
45
|
+
"MemoryUsed",
|
46
|
+
"Vsize",
|
47
|
+
"AvailableDiskSpace",
|
48
|
+
"CPUConsumed",
|
49
|
+
"WallClockTime",
|
50
|
+
}
|
51
|
+
|
41
52
|
# TODO: this is copied from the DIRAC JobDB
|
42
53
|
# but is overwriten in LHCbDIRAC, so we need
|
43
54
|
# to find a way to make it dynamic
|
@@ -156,6 +167,24 @@ class JobDB(BaseSQLDB):
|
|
156
167
|
],
|
157
168
|
)
|
158
169
|
|
170
|
+
@staticmethod
|
171
|
+
def _set_job_attributes_fix_value(column, value):
|
172
|
+
"""Apply corrections to the values before inserting them into the database.
|
173
|
+
|
174
|
+
TODO: Move this logic into the sqlalchemy model.
|
175
|
+
"""
|
176
|
+
if column == "VerifiedFlag":
|
177
|
+
value_str = str(value)
|
178
|
+
if value_str in ("True", "False"):
|
179
|
+
return value_str
|
180
|
+
if column == "AccountedFlag":
|
181
|
+
value_str = str(value)
|
182
|
+
if value_str in ("True", "False", "Failed"):
|
183
|
+
return value_str
|
184
|
+
else:
|
185
|
+
return value
|
186
|
+
raise NotImplementedError(f"Unrecognized value for column {column}: {value}")
|
187
|
+
|
159
188
|
async def set_job_attributes(self, job_data):
|
160
189
|
"""Update the parameters of the given jobs."""
|
161
190
|
# TODO: add myDate and force parameters.
|
@@ -168,7 +197,10 @@ class JobDB(BaseSQLDB):
|
|
168
197
|
case_expressions = {
|
169
198
|
column: case(
|
170
199
|
*[
|
171
|
-
(
|
200
|
+
(
|
201
|
+
Jobs.__table__.c.JobID == job_id,
|
202
|
+
self._set_job_attributes_fix_value(column, attrs[column]),
|
203
|
+
)
|
172
204
|
for job_id, attrs in job_data.items()
|
173
205
|
if column in attrs
|
174
206
|
],
|
@@ -197,7 +229,7 @@ class JobDB(BaseSQLDB):
|
|
197
229
|
|
198
230
|
return {jobid: jdl for jobid, jdl in (await self.conn.execute(stmt)) if jdl}
|
199
231
|
|
200
|
-
async def set_job_commands(self, commands: list[tuple[int, str, str]]):
|
232
|
+
async def set_job_commands(self, commands: list[tuple[int, str, str]]) -> None:
|
201
233
|
"""Store a command to be passed to the job together with the next heart beat."""
|
202
234
|
await self.conn.execute(
|
203
235
|
insert(JobCommands),
|
@@ -246,3 +278,58 @@ class JobDB(BaseSQLDB):
|
|
246
278
|
rows = await self.conn.execute(stmt, update_parameters)
|
247
279
|
|
248
280
|
return rows.rowcount
|
281
|
+
|
282
|
+
async def add_heartbeat_data(
|
283
|
+
self, job_id: int, dynamic_data: dict[str, str]
|
284
|
+
) -> None:
|
285
|
+
"""Add the job's heartbeat data to the database.
|
286
|
+
|
287
|
+
NOTE: This does not update the HeartBeatTime column in the Jobs table.
|
288
|
+
This is instead handled by the `diracx.logic.jobs.status.set_job_statuses`
|
289
|
+
as it involves updating multiple databases.
|
290
|
+
|
291
|
+
:param job_id: the job id
|
292
|
+
:param dynamic_data: mapping of the dynamic data to store,
|
293
|
+
e.g. {"AvailableDiskSpace": 123}
|
294
|
+
"""
|
295
|
+
if extra_fields := set(dynamic_data) - self.heartbeat_fields:
|
296
|
+
raise InvalidQueryError(
|
297
|
+
f"Not allowed to store heartbeat data for: {extra_fields}. "
|
298
|
+
f"Allowed keys are: {self.heartbeat_fields}"
|
299
|
+
)
|
300
|
+
values = [
|
301
|
+
{
|
302
|
+
"JobID": job_id,
|
303
|
+
"Name": key,
|
304
|
+
"Value": value,
|
305
|
+
"HeartBeatTime": utcnow(),
|
306
|
+
}
|
307
|
+
for key, value in dynamic_data.items()
|
308
|
+
]
|
309
|
+
await self.conn.execute(HeartBeatLoggingInfo.__table__.insert().values(values))
|
310
|
+
|
311
|
+
async def get_job_commands(self, job_ids: Iterable[int]) -> list[JobCommand]:
|
312
|
+
"""Get a command to be passed to the job together with the next heartbeat.
|
313
|
+
|
314
|
+
:param job_ids: the job ids
|
315
|
+
:return: mapping of job id to list of commands
|
316
|
+
"""
|
317
|
+
# Get the commands
|
318
|
+
stmt = (
|
319
|
+
select(JobCommands.job_id, JobCommands.command, JobCommands.arguments)
|
320
|
+
.where(JobCommands.job_id.in_(job_ids), JobCommands.status == "Received")
|
321
|
+
.order_by(JobCommands.job_id)
|
322
|
+
)
|
323
|
+
commands = await self.conn.execute(stmt)
|
324
|
+
# Update the status of the commands
|
325
|
+
stmt = (
|
326
|
+
update(JobCommands)
|
327
|
+
.where(JobCommands.job_id.in_(job_ids))
|
328
|
+
.values(Status="Sent")
|
329
|
+
)
|
330
|
+
await self.conn.execute(stmt)
|
331
|
+
# Return the commands grouped by job id
|
332
|
+
return [
|
333
|
+
JobCommand(job_id=cmd.JobID, command=cmd.Command, arguments=cmd.Arguments)
|
334
|
+
for cmd in commands
|
335
|
+
]
|
diracx/db/sql/job/schema.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import sqlalchemy.types as types
|
3
4
|
from sqlalchemy import (
|
4
5
|
DateTime,
|
5
|
-
Enum,
|
6
6
|
ForeignKey,
|
7
7
|
Index,
|
8
8
|
Integer,
|
@@ -16,6 +16,36 @@ from ..utils import Column, EnumBackedBool, NullColumn
|
|
16
16
|
JobDBBase = declarative_base()
|
17
17
|
|
18
18
|
|
19
|
+
class AccountedFlagEnum(types.TypeDecorator):
|
20
|
+
"""Maps a ``AccountedFlagEnum()`` column to True/False in Python."""
|
21
|
+
|
22
|
+
impl = types.Enum
|
23
|
+
cache_ok: bool = True
|
24
|
+
|
25
|
+
def __init__(self) -> None:
|
26
|
+
super().__init__("True", "False", "Failed")
|
27
|
+
|
28
|
+
def process_bind_param(self, value, dialect) -> str:
|
29
|
+
if value is True:
|
30
|
+
return "True"
|
31
|
+
elif value is False:
|
32
|
+
return "False"
|
33
|
+
elif value == "Failed":
|
34
|
+
return "Failed"
|
35
|
+
else:
|
36
|
+
raise NotImplementedError(value, dialect)
|
37
|
+
|
38
|
+
def process_result_value(self, value, dialect) -> bool | str:
|
39
|
+
if value == "True":
|
40
|
+
return True
|
41
|
+
elif value == "False":
|
42
|
+
return False
|
43
|
+
elif value == "Failed":
|
44
|
+
return "Failed"
|
45
|
+
else:
|
46
|
+
raise NotImplementedError(f"Unknown {value=}")
|
47
|
+
|
48
|
+
|
19
49
|
class Jobs(JobDBBase):
|
20
50
|
__tablename__ = "Jobs"
|
21
51
|
|
@@ -45,10 +75,7 @@ class Jobs(JobDBBase):
|
|
45
75
|
user_priority = Column("UserPriority", Integer, default=0)
|
46
76
|
reschedule_counter = Column("RescheduleCounter", Integer, default=0)
|
47
77
|
verified_flag = Column("VerifiedFlag", EnumBackedBool(), default=False)
|
48
|
-
|
49
|
-
accounted_flag = Column(
|
50
|
-
"AccountedFlag", Enum("True", "False", "Failed"), default="False"
|
51
|
-
)
|
78
|
+
accounted_flag = Column("AccountedFlag", AccountedFlagEnum(), default=False)
|
52
79
|
|
53
80
|
__table_args__ = (
|
54
81
|
Index("JobType", "JobType"),
|
@@ -3,8 +3,8 @@ 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=
|
7
|
-
diracx/db/os/utils.py,sha256=
|
6
|
+
diracx/db/os/job_parameters.py,sha256=W-2O0cnq91iExcQAEqnCKuA9Ko_6MQaTGALGG8ziQ-4,1149
|
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
|
10
10
|
diracx/db/sql/auth/db.py,sha256=O9sp-AKobQ4X4IF2WaJKig-8LU8Ra9Syn5jW8JNLRU4,9030
|
@@ -13,8 +13,8 @@ diracx/db/sql/dummy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
13
13
|
diracx/db/sql/dummy/db.py,sha256=whcF02IjFQTwe1l4iTHtCkg2XukEaLHpLeHYNGMuGGw,1645
|
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=
|
17
|
-
diracx/db/sql/job/schema.py,sha256=
|
16
|
+
diracx/db/sql/job/db.py,sha256=PTivVrSl4hrOTgy77WECunPJUJiYzGhSlqO0wYwEuE8,11909
|
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=0WuRn3pKEeuV9IZ_3VDhlvqI9eM1W4C0dZwIVZKW9N8,5811
|
20
20
|
diracx/db/sql/job_logging/schema.py,sha256=DyFr_4goxfC453xPJX4OkxTGHtlP_5q8PXpr8bhNQS0,960
|
@@ -31,8 +31,8 @@ diracx/db/sql/utils/__init__.py,sha256=QkvpqBuIAgkAOywAssYzdxSzUQVZlSUumK7mPxotX
|
|
31
31
|
diracx/db/sql/utils/base.py,sha256=7UxHBNLOSjdrIdslMKW4C_c5H9-6Y1BEimxscri2poE,12367
|
32
32
|
diracx/db/sql/utils/functions.py,sha256=iLqlUIQ6SrDUtDEnZ5szaFbdcINJW15KNbCdGXss6kc,3055
|
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.
|
34
|
+
diracx_db-0.0.1a35.dist-info/METADATA,sha256=9KlPHZrT1qCFKxJMs6uagTjk8VHZBbKRls7sw2dHqog,644
|
35
|
+
diracx_db-0.0.1a35.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
36
|
+
diracx_db-0.0.1a35.dist-info/entry_points.txt,sha256=UPqhLvb9gui0kOyWeI_edtefcrHToZmQt1p76vIwujo,317
|
37
|
+
diracx_db-0.0.1a35.dist-info/top_level.txt,sha256=vJx10tdRlBX3rF2Psgk5jlwVGZNcL3m_7iQWwgPXt-U,7
|
38
|
+
diracx_db-0.0.1a35.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|