diracx-db 0.0.1a12__tar.gz → 0.0.1a13__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/PKG-INFO +1 -1
  2. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/jobs/status_utility.py +1 -1
  3. diracx-db-0.0.1a13/src/diracx/db/sql/sandbox_metadata/db.py +169 -0
  4. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx_db.egg-info/PKG-INFO +1 -1
  5. diracx-db-0.0.1a13/tests/test_sandbox_metadata.py +173 -0
  6. diracx-db-0.0.1a12/src/diracx/db/sql/sandbox_metadata/db.py +0 -96
  7. diracx-db-0.0.1a12/tests/test_sandbox_metadata.py +0 -92
  8. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/README.md +0 -0
  9. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/pyproject.toml +0 -0
  10. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/setup.cfg +0 -0
  11. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/__init__.py +0 -0
  12. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/__main__.py +0 -0
  13. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/exceptions.py +0 -0
  14. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/os/__init__.py +0 -0
  15. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/os/job_parameters.py +0 -0
  16. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/os/utils.py +0 -0
  17. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/py.typed +0 -0
  18. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/__init__.py +0 -0
  19. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/auth/__init__.py +0 -0
  20. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/auth/db.py +0 -0
  21. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/auth/schema.py +0 -0
  22. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/dummy/__init__.py +0 -0
  23. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/dummy/db.py +0 -0
  24. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/dummy/schema.py +0 -0
  25. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/jobs/__init__.py +0 -0
  26. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/jobs/db.py +0 -0
  27. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/jobs/schema.py +0 -0
  28. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/sandbox_metadata/__init__.py +0 -0
  29. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/sandbox_metadata/schema.py +0 -0
  30. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx/db/sql/utils.py +0 -0
  31. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx_db.egg-info/SOURCES.txt +0 -0
  32. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx_db.egg-info/dependency_links.txt +0 -0
  33. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx_db.egg-info/entry_points.txt +0 -0
  34. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx_db.egg-info/requires.txt +0 -0
  35. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/src/diracx_db.egg-info/top_level.txt +0 -0
  36. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/auth/test_authorization_flow.py +0 -0
  37. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/auth/test_device_flow.py +0 -0
  38. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/auth/test_refresh_token.py +0 -0
  39. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/jobs/test_jobDB.py +0 -0
  40. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/jobs/test_jobLoggingDB.py +0 -0
  41. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/opensearch/test_connection.py +0 -0
  42. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/opensearch/test_index_template.py +0 -0
  43. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/opensearch/test_search.py +0 -0
  44. {diracx-db-0.0.1a12 → diracx-db-0.0.1a13}/tests/test_dummyDB.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: diracx-db
3
- Version: 0.0.1a12
3
+ Version: 0.0.1a13
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -272,7 +272,7 @@ async def remove_jobs(
272
272
 
273
273
  # TODO: this was also not done in the JobManagerHandler, but it was done in the JobCleaningAgent
274
274
  # I think it should be done here as well
275
- await sandbox_metadata_db.unassign_sandbox_from_jobs(job_ids)
275
+ await sandbox_metadata_db.unassign_sandboxes_to_jobs(job_ids)
276
276
 
277
277
  # Remove the job from TaskQueueDB
278
278
  await _remove_jobs_from_task_queue(job_ids, config, task_queue_db, background_task)
@@ -0,0 +1,169 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import sqlalchemy
6
+
7
+ from diracx.core.models import SandboxInfo, SandboxType, UserInfo
8
+ from diracx.db.sql.utils import BaseSQLDB, utcnow
9
+
10
+ from .schema import Base as SandboxMetadataDBBase
11
+ from .schema import sb_EntityMapping, sb_Owners, sb_SandBoxes
12
+
13
+
14
+ class SandboxMetadataDB(BaseSQLDB):
15
+ metadata = SandboxMetadataDBBase.metadata
16
+
17
+ async def upsert_owner(self, user: UserInfo) -> int:
18
+ """Get the id of the owner from the database"""
19
+ # TODO: Follow https://github.com/DIRACGrid/diracx/issues/49
20
+ stmt = sqlalchemy.select(sb_Owners.OwnerID).where(
21
+ sb_Owners.Owner == user.preferred_username,
22
+ sb_Owners.OwnerGroup == user.dirac_group,
23
+ sb_Owners.VO == user.vo,
24
+ )
25
+ result = await self.conn.execute(stmt)
26
+ if owner_id := result.scalar_one_or_none():
27
+ return owner_id
28
+
29
+ stmt = sqlalchemy.insert(sb_Owners).values(
30
+ Owner=user.preferred_username,
31
+ OwnerGroup=user.dirac_group,
32
+ VO=user.vo,
33
+ )
34
+ result = await self.conn.execute(stmt)
35
+ return result.lastrowid
36
+
37
+ @staticmethod
38
+ def get_pfn(bucket_name: str, user: UserInfo, sandbox_info: SandboxInfo) -> str:
39
+ """Get the sandbox's user namespaced and content addressed PFN"""
40
+ parts = [
41
+ "S3",
42
+ bucket_name,
43
+ user.vo,
44
+ user.dirac_group,
45
+ user.preferred_username,
46
+ f"{sandbox_info.checksum_algorithm}:{sandbox_info.checksum}.{sandbox_info.format}",
47
+ ]
48
+ return "/" + "/".join(parts)
49
+
50
+ async def insert_sandbox(
51
+ self, se_name: str, user: UserInfo, pfn: str, size: int
52
+ ) -> None:
53
+ """Add a new sandbox in SandboxMetadataDB"""
54
+ # TODO: Follow https://github.com/DIRACGrid/diracx/issues/49
55
+ owner_id = await self.upsert_owner(user)
56
+ stmt = sqlalchemy.insert(sb_SandBoxes).values(
57
+ OwnerId=owner_id,
58
+ SEName=se_name,
59
+ SEPFN=pfn,
60
+ Bytes=size,
61
+ RegistrationTime=utcnow(),
62
+ LastAccessTime=utcnow(),
63
+ )
64
+ try:
65
+ result = await self.conn.execute(stmt)
66
+ except sqlalchemy.exc.IntegrityError:
67
+ await self.update_sandbox_last_access_time(se_name, pfn)
68
+ else:
69
+ assert result.rowcount == 1
70
+
71
+ async def update_sandbox_last_access_time(self, se_name: str, pfn: str) -> None:
72
+ stmt = (
73
+ sqlalchemy.update(sb_SandBoxes)
74
+ .where(sb_SandBoxes.SEName == se_name, sb_SandBoxes.SEPFN == pfn)
75
+ .values(LastAccessTime=utcnow())
76
+ )
77
+ result = await self.conn.execute(stmt)
78
+ assert result.rowcount == 1
79
+
80
+ async def sandbox_is_assigned(self, pfn: str, se_name: str) -> bool:
81
+ """Checks if a sandbox exists and has been assigned."""
82
+ stmt: sqlalchemy.Executable = sqlalchemy.select(sb_SandBoxes.Assigned).where(
83
+ sb_SandBoxes.SEName == se_name, sb_SandBoxes.SEPFN == pfn
84
+ )
85
+ result = await self.conn.execute(stmt)
86
+ is_assigned = result.scalar_one()
87
+ return is_assigned
88
+
89
+ @staticmethod
90
+ def jobid_to_entity_id(job_id: int) -> str:
91
+ """Define the entity id as 'Entity:entity_id' due to the DB definition"""
92
+ return f"Job:{job_id}"
93
+
94
+ async def get_sandbox_assigned_to_job(
95
+ self, job_id: int, sb_type: SandboxType
96
+ ) -> list[Any]:
97
+ """Get the sandbox assign to job"""
98
+ entity_id = self.jobid_to_entity_id(job_id)
99
+ stmt = (
100
+ sqlalchemy.select(sb_SandBoxes.SEPFN)
101
+ .where(sb_SandBoxes.SBId == sb_EntityMapping.SBId)
102
+ .where(
103
+ sb_EntityMapping.EntityId == entity_id,
104
+ sb_EntityMapping.Type == sb_type,
105
+ )
106
+ )
107
+ result = await self.conn.execute(stmt)
108
+ return [result.scalar()]
109
+
110
+ async def assign_sandbox_to_jobs(
111
+ self,
112
+ jobs_ids: list[int],
113
+ pfn: str,
114
+ sb_type: SandboxType,
115
+ se_name: str,
116
+ ) -> None:
117
+ """Mapp sandbox and jobs"""
118
+ for job_id in jobs_ids:
119
+ # Define the entity id as 'Entity:entity_id' due to the DB definition:
120
+ entity_id = self.jobid_to_entity_id(job_id)
121
+ select_sb_id = sqlalchemy.select(
122
+ sb_SandBoxes.SBId,
123
+ sqlalchemy.literal(entity_id).label("EntityId"),
124
+ sqlalchemy.literal(sb_type).label("Type"),
125
+ ).where(
126
+ sb_SandBoxes.SEName == se_name,
127
+ sb_SandBoxes.SEPFN == pfn,
128
+ )
129
+ stmt = sqlalchemy.insert(sb_EntityMapping).from_select(
130
+ ["SBId", "EntityId", "Type"], select_sb_id
131
+ )
132
+ await self.conn.execute(stmt)
133
+
134
+ stmt = (
135
+ sqlalchemy.update(sb_SandBoxes)
136
+ .where(sb_SandBoxes.SEPFN == pfn)
137
+ .values(Assigned=True)
138
+ )
139
+ result = await self.conn.execute(stmt)
140
+ assert result.rowcount == 1
141
+
142
+ async def unassign_sandboxes_to_jobs(self, jobs_ids: list[int]) -> None:
143
+ """Delete mapping between jobs and sandboxes"""
144
+ for job_id in jobs_ids:
145
+ entity_id = self.jobid_to_entity_id(job_id)
146
+ sb_sel_stmt = sqlalchemy.select(
147
+ sb_SandBoxes.SBId,
148
+ ).where(sb_EntityMapping.EntityId == entity_id)
149
+
150
+ result = await self.conn.execute(sb_sel_stmt)
151
+ sb_ids = [row.SBId for row in result]
152
+
153
+ del_stmt = sqlalchemy.delete(sb_EntityMapping).where(
154
+ sb_EntityMapping.EntityId == entity_id
155
+ )
156
+ await self.conn.execute(del_stmt)
157
+
158
+ sb_entity_sel_stmt = sqlalchemy.select(sb_EntityMapping.SBId).where(
159
+ sb_EntityMapping.SBId.in_(sb_ids)
160
+ )
161
+ result = await self.conn.execute(sb_entity_sel_stmt)
162
+ remaining_sb_ids = [row.SBId for row in result]
163
+ if not remaining_sb_ids:
164
+ unassign_stmt = (
165
+ sqlalchemy.update(sb_SandBoxes)
166
+ .where(sb_SandBoxes.SBId.in_(sb_ids))
167
+ .values(Assigned=False)
168
+ )
169
+ await self.conn.execute(unassign_stmt)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: diracx-db
3
- Version: 0.0.1a12
3
+ Version: 0.0.1a13
4
4
  Summary: TODO
5
5
  License: GPL-3.0-only
6
6
  Classifier: Intended Audience :: Science/Research
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import secrets
5
+ from datetime import datetime
6
+
7
+ import pytest
8
+ import sqlalchemy
9
+
10
+ from diracx.core.models import SandboxInfo, UserInfo
11
+ from diracx.db.sql.sandbox_metadata.db import SandboxMetadataDB
12
+ from diracx.db.sql.sandbox_metadata.schema import sb_EntityMapping, sb_SandBoxes
13
+
14
+
15
+ @pytest.fixture
16
+ async def sandbox_metadata_db(tmp_path):
17
+ sandbox_metadata_db = SandboxMetadataDB("sqlite+aiosqlite:///:memory:")
18
+ async with sandbox_metadata_db.engine_context():
19
+ async with sandbox_metadata_db.engine.begin() as conn:
20
+ await conn.run_sync(sandbox_metadata_db.metadata.create_all)
21
+ yield sandbox_metadata_db
22
+
23
+
24
+ def test_get_pfn(sandbox_metadata_db: SandboxMetadataDB):
25
+ user_info = UserInfo(
26
+ sub="vo:sub", preferred_username="user1", dirac_group="group1", vo="vo"
27
+ )
28
+ sandbox_info = SandboxInfo(
29
+ checksum="checksum",
30
+ checksum_algorithm="sha256",
31
+ format="tar.bz2",
32
+ size=100,
33
+ )
34
+ pfn = sandbox_metadata_db.get_pfn("bucket1", user_info, sandbox_info)
35
+ assert pfn == "/S3/bucket1/vo/group1/user1/sha256:checksum.tar.bz2"
36
+
37
+
38
+ async def test_insert_sandbox(sandbox_metadata_db: SandboxMetadataDB):
39
+ user_info = UserInfo(
40
+ sub="vo:sub", preferred_username="user1", dirac_group="group1", vo="vo"
41
+ )
42
+ pfn1 = secrets.token_hex()
43
+
44
+ # Make sure the sandbox doesn't already exist
45
+ db_contents = await _dump_db(sandbox_metadata_db)
46
+ assert pfn1 not in db_contents
47
+ async with sandbox_metadata_db:
48
+ with pytest.raises(sqlalchemy.exc.NoResultFound):
49
+ await sandbox_metadata_db.sandbox_is_assigned(pfn1, "SandboxSE")
50
+
51
+ # Insert the sandbox
52
+ async with sandbox_metadata_db:
53
+ await sandbox_metadata_db.insert_sandbox("SandboxSE", user_info, pfn1, 100)
54
+ db_contents = await _dump_db(sandbox_metadata_db)
55
+ owner_id1, last_access_time1 = db_contents[pfn1]
56
+
57
+ # Inserting again should update the last access time
58
+ await asyncio.sleep(1) # The timestamp only has second precision
59
+ async with sandbox_metadata_db:
60
+ await sandbox_metadata_db.insert_sandbox("SandboxSE", user_info, pfn1, 100)
61
+ db_contents = await _dump_db(sandbox_metadata_db)
62
+ owner_id2, last_access_time2 = db_contents[pfn1]
63
+ assert owner_id1 == owner_id2
64
+ assert last_access_time2 > last_access_time1
65
+
66
+ # The sandbox still hasn't been assigned
67
+ async with sandbox_metadata_db:
68
+ assert not await sandbox_metadata_db.sandbox_is_assigned(pfn1, "SandboxSE")
69
+
70
+ # Inserting again should update the last access time
71
+ await asyncio.sleep(1) # The timestamp only has second precision
72
+ last_access_time3 = (await _dump_db(sandbox_metadata_db))[pfn1][1]
73
+ assert last_access_time2 == last_access_time3
74
+ async with sandbox_metadata_db:
75
+ await sandbox_metadata_db.update_sandbox_last_access_time("SandboxSE", pfn1)
76
+ last_access_time4 = (await _dump_db(sandbox_metadata_db))[pfn1][1]
77
+ assert last_access_time2 < last_access_time4
78
+
79
+
80
+ async def _dump_db(
81
+ sandbox_metadata_db: SandboxMetadataDB,
82
+ ) -> dict[str, tuple[int, datetime]]:
83
+ """Dump the contents of the sandbox metadata database
84
+
85
+ Returns a dict[pfn: str, (owner_id: int, last_access_time: datetime)]
86
+ """
87
+ async with sandbox_metadata_db:
88
+ stmt = sqlalchemy.select(
89
+ sb_SandBoxes.SEPFN, sb_SandBoxes.OwnerId, sb_SandBoxes.LastAccessTime
90
+ )
91
+ res = await sandbox_metadata_db.conn.execute(stmt)
92
+ return {row.SEPFN: (row.OwnerId, row.LastAccessTime) for row in res}
93
+
94
+
95
+ async def test_assign_and_unsassign_sandbox_to_jobs(
96
+ sandbox_metadata_db: SandboxMetadataDB,
97
+ ):
98
+ pfn = secrets.token_hex()
99
+ user_info = UserInfo(
100
+ sub="vo:sub", preferred_username="user1", dirac_group="group1", vo="vo"
101
+ )
102
+ dummy_jobid = 666
103
+ sandbox_se = "SandboxSE"
104
+ # Insert the sandbox
105
+ async with sandbox_metadata_db:
106
+ await sandbox_metadata_db.insert_sandbox(sandbox_se, user_info, pfn, 100)
107
+
108
+ async with sandbox_metadata_db:
109
+ stmt = sqlalchemy.select(sb_SandBoxes.SBId, sb_SandBoxes.SEPFN)
110
+ res = await sandbox_metadata_db.conn.execute(stmt)
111
+ db_contents = {row.SEPFN: row.SBId for row in res}
112
+ sb_id_1 = db_contents[pfn]
113
+ # The sandbox still hasn't been assigned
114
+ async with sandbox_metadata_db:
115
+ assert not await sandbox_metadata_db.sandbox_is_assigned(pfn, sandbox_se)
116
+
117
+ # Check there is no mapping
118
+ async with sandbox_metadata_db:
119
+ stmt = sqlalchemy.select(
120
+ sb_EntityMapping.SBId, sb_EntityMapping.EntityId, sb_EntityMapping.Type
121
+ )
122
+ res = await sandbox_metadata_db.conn.execute(stmt)
123
+ db_contents = {row.SBId: (row.EntityId, row.Type) for row in res}
124
+ assert db_contents == {}
125
+
126
+ # Assign sandbox with dummy jobid
127
+ async with sandbox_metadata_db:
128
+ await sandbox_metadata_db.assign_sandbox_to_jobs(
129
+ jobs_ids=[dummy_jobid], pfn=pfn, sb_type="Output", se_name=sandbox_se
130
+ )
131
+ # Check if sandbox and job are mapped
132
+ async with sandbox_metadata_db:
133
+ stmt = sqlalchemy.select(
134
+ sb_EntityMapping.SBId, sb_EntityMapping.EntityId, sb_EntityMapping.Type
135
+ )
136
+ res = await sandbox_metadata_db.conn.execute(stmt)
137
+ db_contents = {row.SBId: (row.EntityId, row.Type) for row in res}
138
+
139
+ entity_id_1, sb_type = db_contents[sb_id_1]
140
+ assert entity_id_1 == f"Job:{dummy_jobid}"
141
+ assert sb_type == "Output"
142
+
143
+ async with sandbox_metadata_db:
144
+ stmt = sqlalchemy.select(sb_SandBoxes.SBId, sb_SandBoxes.SEPFN)
145
+ res = await sandbox_metadata_db.conn.execute(stmt)
146
+ db_contents = {row.SEPFN: row.SBId for row in res}
147
+ sb_id_1 = db_contents[pfn]
148
+ # The sandbox should be assigned
149
+ async with sandbox_metadata_db:
150
+ assert await sandbox_metadata_db.sandbox_is_assigned(pfn, sandbox_se)
151
+
152
+ # Unassign the sandbox to job
153
+ async with sandbox_metadata_db:
154
+ await sandbox_metadata_db.unassign_sandboxes_to_jobs([dummy_jobid])
155
+
156
+ # Entity should not exists anymore
157
+ async with sandbox_metadata_db:
158
+ stmt = sqlalchemy.select(sb_EntityMapping.SBId).where(
159
+ sb_EntityMapping.EntityId == entity_id_1
160
+ )
161
+ res = await sandbox_metadata_db.conn.execute(stmt)
162
+ entity_sb_id = [row.SBId for row in res]
163
+ assert entity_sb_id == []
164
+
165
+ # Should not be assigned anymore
166
+ async with sandbox_metadata_db:
167
+ assert await sandbox_metadata_db.sandbox_is_assigned(pfn, sandbox_se) is False
168
+ # Check the mapping has been deleted
169
+ async with sandbox_metadata_db:
170
+ stmt = sqlalchemy.select(sb_EntityMapping.SBId)
171
+ res = await sandbox_metadata_db.conn.execute(stmt)
172
+ res_sb_id = [row.SBId for row in res]
173
+ assert sb_id_1 not in res_sb_id
@@ -1,96 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import sqlalchemy
4
- from sqlalchemy import delete
5
-
6
- from diracx.core.models import SandboxInfo, UserInfo
7
- from diracx.db.sql.utils import BaseSQLDB, utcnow
8
-
9
- from .schema import Base as SandboxMetadataDBBase
10
- from .schema import sb_EntityMapping, sb_Owners, sb_SandBoxes
11
-
12
-
13
- class SandboxMetadataDB(BaseSQLDB):
14
- metadata = SandboxMetadataDBBase.metadata
15
-
16
- async def upsert_owner(self, user: UserInfo) -> int:
17
- """Get the id of the owner from the database"""
18
- # TODO: Follow https://github.com/DIRACGrid/diracx/issues/49
19
- stmt = sqlalchemy.select(sb_Owners.OwnerID).where(
20
- sb_Owners.Owner == user.preferred_username,
21
- sb_Owners.OwnerGroup == user.dirac_group,
22
- sb_Owners.VO == user.vo,
23
- )
24
- result = await self.conn.execute(stmt)
25
- if owner_id := result.scalar_one_or_none():
26
- return owner_id
27
-
28
- stmt = sqlalchemy.insert(sb_Owners).values(
29
- Owner=user.preferred_username,
30
- OwnerGroup=user.dirac_group,
31
- VO=user.vo,
32
- )
33
- result = await self.conn.execute(stmt)
34
- return result.lastrowid
35
-
36
- @staticmethod
37
- def get_pfn(bucket_name: str, user: UserInfo, sandbox_info: SandboxInfo) -> str:
38
- """Get the sandbox's user namespaced and content addressed PFN"""
39
- parts = [
40
- "S3",
41
- bucket_name,
42
- user.vo,
43
- user.dirac_group,
44
- user.preferred_username,
45
- f"{sandbox_info.checksum_algorithm}:{sandbox_info.checksum}.{sandbox_info.format}",
46
- ]
47
- return "/" + "/".join(parts)
48
-
49
- async def insert_sandbox(
50
- self, se_name: str, user: UserInfo, pfn: str, size: int
51
- ) -> None:
52
- """Add a new sandbox in SandboxMetadataDB"""
53
- # TODO: Follow https://github.com/DIRACGrid/diracx/issues/49
54
- owner_id = await self.upsert_owner(user)
55
- stmt = sqlalchemy.insert(sb_SandBoxes).values(
56
- OwnerId=owner_id,
57
- SEName=se_name,
58
- SEPFN=pfn,
59
- Bytes=size,
60
- RegistrationTime=utcnow(),
61
- LastAccessTime=utcnow(),
62
- )
63
- try:
64
- result = await self.conn.execute(stmt)
65
- except sqlalchemy.exc.IntegrityError:
66
- await self.update_sandbox_last_access_time(se_name, pfn)
67
- else:
68
- assert result.rowcount == 1
69
-
70
- async def update_sandbox_last_access_time(self, se_name: str, pfn: str) -> None:
71
- stmt = (
72
- sqlalchemy.update(sb_SandBoxes)
73
- .where(sb_SandBoxes.SEName == se_name, sb_SandBoxes.SEPFN == pfn)
74
- .values(LastAccessTime=utcnow())
75
- )
76
- result = await self.conn.execute(stmt)
77
- assert result.rowcount == 1
78
-
79
- async def sandbox_is_assigned(self, se_name: str, pfn: str) -> bool:
80
- """Checks if a sandbox exists and has been assigned."""
81
- stmt: sqlalchemy.Executable = sqlalchemy.select(sb_SandBoxes.Assigned).where(
82
- sb_SandBoxes.SEName == se_name, sb_SandBoxes.SEPFN == pfn
83
- )
84
- result = await self.conn.execute(stmt)
85
- is_assigned = result.scalar_one()
86
- return is_assigned
87
- return True
88
-
89
- async def unassign_sandbox_from_jobs(self, job_ids: list[int]):
90
- """
91
- Unassign sandbox from jobs
92
- """
93
- stmt = delete(sb_EntityMapping).where(
94
- sb_EntityMapping.EntityId.in_(f"Job:{job_id}" for job_id in job_ids)
95
- )
96
- await self.conn.execute(stmt)
@@ -1,92 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import secrets
5
- from datetime import datetime
6
-
7
- import pytest
8
- import sqlalchemy
9
-
10
- from diracx.core.models import SandboxInfo, UserInfo
11
- from diracx.db.sql.sandbox_metadata.db import SandboxMetadataDB
12
- from diracx.db.sql.sandbox_metadata.schema import sb_SandBoxes
13
-
14
-
15
- @pytest.fixture
16
- async def sandbox_metadata_db(tmp_path):
17
- sandbox_metadata_db = SandboxMetadataDB("sqlite+aiosqlite:///:memory:")
18
- async with sandbox_metadata_db.engine_context():
19
- async with sandbox_metadata_db.engine.begin() as conn:
20
- await conn.run_sync(sandbox_metadata_db.metadata.create_all)
21
- yield sandbox_metadata_db
22
-
23
-
24
- def test_get_pfn(sandbox_metadata_db: SandboxMetadataDB):
25
- user_info = UserInfo(
26
- sub="vo:sub", preferred_username="user1", dirac_group="group1", vo="vo"
27
- )
28
- sandbox_info = SandboxInfo(
29
- checksum="checksum",
30
- checksum_algorithm="sha256",
31
- format="tar.bz2",
32
- size=100,
33
- )
34
- pfn = sandbox_metadata_db.get_pfn("bucket1", user_info, sandbox_info)
35
- assert pfn == "/S3/bucket1/vo/group1/user1/sha256:checksum.tar.bz2"
36
-
37
-
38
- async def test_insert_sandbox(sandbox_metadata_db: SandboxMetadataDB):
39
- user_info = UserInfo(
40
- sub="vo:sub", preferred_username="user1", dirac_group="group1", vo="vo"
41
- )
42
- pfn1 = secrets.token_hex()
43
-
44
- # Make sure the sandbox doesn't already exist
45
- db_contents = await _dump_db(sandbox_metadata_db)
46
- assert pfn1 not in db_contents
47
- async with sandbox_metadata_db:
48
- with pytest.raises(sqlalchemy.exc.NoResultFound):
49
- await sandbox_metadata_db.sandbox_is_assigned("SandboxSE", pfn1)
50
-
51
- # Insert the sandbox
52
- async with sandbox_metadata_db:
53
- await sandbox_metadata_db.insert_sandbox("SandboxSE", user_info, pfn1, 100)
54
- db_contents = await _dump_db(sandbox_metadata_db)
55
- owner_id1, last_access_time1 = db_contents[pfn1]
56
-
57
- # Inserting again should update the last access time
58
- await asyncio.sleep(1) # The timestamp only has second precision
59
- async with sandbox_metadata_db:
60
- await sandbox_metadata_db.insert_sandbox("SandboxSE", user_info, pfn1, 100)
61
- db_contents = await _dump_db(sandbox_metadata_db)
62
- owner_id2, last_access_time2 = db_contents[pfn1]
63
- assert owner_id1 == owner_id2
64
- assert last_access_time2 > last_access_time1
65
-
66
- # The sandbox still hasn't been assigned
67
- async with sandbox_metadata_db:
68
- assert not await sandbox_metadata_db.sandbox_is_assigned("SandboxSE", pfn1)
69
-
70
- # Inserting again should update the last access time
71
- await asyncio.sleep(1) # The timestamp only has second precision
72
- last_access_time3 = (await _dump_db(sandbox_metadata_db))[pfn1][1]
73
- assert last_access_time2 == last_access_time3
74
- async with sandbox_metadata_db:
75
- await sandbox_metadata_db.update_sandbox_last_access_time("SandboxSE", pfn1)
76
- last_access_time4 = (await _dump_db(sandbox_metadata_db))[pfn1][1]
77
- assert last_access_time2 < last_access_time4
78
-
79
-
80
- async def _dump_db(
81
- sandbox_metadata_db: SandboxMetadataDB,
82
- ) -> dict[str, tuple[int, datetime]]:
83
- """Dump the contents of the sandbox metadata database
84
-
85
- Returns a dict[pfn: str, (owner_id: int, last_access_time: datetime)]
86
- """
87
- async with sandbox_metadata_db:
88
- stmt = sqlalchemy.select(
89
- sb_SandBoxes.SEPFN, sb_SandBoxes.OwnerId, sb_SandBoxes.LastAccessTime
90
- )
91
- res = await sandbox_metadata_db.conn.execute(stmt)
92
- return {row.SEPFN: (row.OwnerId, row.LastAccessTime) for row in res}
File without changes
File without changes