|
1 | | -""" SandboxMetadataDB frontend |
2 | | -""" |
3 | | - |
4 | 1 | from __future__ import annotations |
5 | 2 |
|
6 | | -import datetime |
7 | | - |
8 | 3 | import sqlalchemy |
9 | 4 |
|
10 | | -from diracx.db.sql.utils import BaseSQLDB |
| 5 | +from diracx.core.models import SandboxInfo, UserInfo |
| 6 | +from diracx.db.sql.utils import BaseSQLDB, utcnow |
11 | 7 |
|
12 | 8 | from .schema import Base as SandboxMetadataDBBase |
13 | 9 | from .schema import sb_Owners, sb_SandBoxes |
14 | 10 |
|
15 | 11 | # In legacy DIRAC the SEName column was used to support multiple different |
16 | 12 | # storage backends. This is no longer the case, so we hardcode the value to |
17 | 13 | # S3 to represent the new DiracX system. |
18 | | -SE_NAME = "S3" |
| 14 | +SE_NAME = "ProductionSandboxSE" |
| 15 | +PFN_PREFIX = "/S3/" |
19 | 16 |
|
20 | 17 |
|
21 | 18 | class SandboxMetadataDB(BaseSQLDB): |
22 | 19 | metadata = SandboxMetadataDBBase.metadata |
23 | 20 |
|
24 | | - async def _get_put_owner(self, owner: str, owner_group: str) -> int: |
25 | | - """adds a new owner/ownerGroup pairs, while returning their ID if already existing |
26 | | -
|
27 | | - Args: |
28 | | - owner (str): user name |
29 | | - owner_group (str): group of the owner |
30 | | - """ |
| 21 | + async def upsert_owner(self, user: UserInfo) -> int: |
| 22 | + """Get the id of the owner from the database""" |
| 23 | + # TODO: Follow https://github.com/DIRACGrid/diracx/issues/49 |
31 | 24 | stmt = sqlalchemy.select(sb_Owners.OwnerID).where( |
32 | | - sb_Owners.Owner == owner, sb_Owners.OwnerGroup == owner_group |
| 25 | + sb_Owners.Owner == user.preferred_username, |
| 26 | + sb_Owners.OwnerGroup == user.dirac_group, |
| 27 | + # TODO: Add VO |
33 | 28 | ) |
34 | 29 | result = await self.conn.execute(stmt) |
35 | 30 | if owner_id := result.scalar_one_or_none(): |
36 | 31 | return owner_id |
37 | 32 |
|
38 | | - stmt = sqlalchemy.insert(sb_Owners).values(Owner=owner, OwnerGroup=owner_group) |
| 33 | + stmt = sqlalchemy.insert(sb_Owners).values( |
| 34 | + Owner=user.preferred_username, |
| 35 | + OwnerGroup=user.dirac_group, |
| 36 | + ) |
39 | 37 | result = await self.conn.execute(stmt) |
40 | 38 | return result.lastrowid |
41 | 39 |
|
42 | | - async def insert( |
43 | | - self, owner: str, owner_group: str, sb_SE: str, se_PFN: str, size: int = 0 |
44 | | - ) -> int: |
45 | | - """inserts a new sandbox in SandboxMetadataDB |
46 | | - this is "equivalent" of DIRAC registerAndGetSandbox |
47 | | -
|
48 | | - Args: |
49 | | - owner (str): user name_ |
50 | | - owner_group (str): groupd of the owner |
51 | | - sb_SE (str): _description_ |
52 | | - sb_PFN (str): _description_ |
53 | | - size (int, optional): _description_. Defaults to 0. |
54 | | - """ |
55 | | - owner_id = await self._get_put_owner(owner, owner_group) |
| 40 | + @staticmethod |
| 41 | + def get_pfn(bucket_name: str, user: UserInfo, sandbox_info: SandboxInfo) -> str: |
| 42 | + """Get the sandbox's user namespaced and content addressed PFN""" |
| 43 | + parts = [ |
| 44 | + "S3", |
| 45 | + bucket_name, |
| 46 | + user.vo, |
| 47 | + user.dirac_group, |
| 48 | + user.preferred_username, |
| 49 | + f"{sandbox_info.checksum_algorithm}:{sandbox_info.checksum}.{sandbox_info.format}", |
| 50 | + ] |
| 51 | + return "/".join(parts) |
| 52 | + |
| 53 | + async def insert_sandbox(self, user: UserInfo, pfn: str, size: int): |
| 54 | + """Add a new sandbox in SandboxMetadataDB""" |
| 55 | + # TODO: Follow https://github.com/DIRACGrid/diracx/issues/49 |
| 56 | + owner_id = await self.upsert_owner(user) |
56 | 57 | stmt = sqlalchemy.insert(sb_SandBoxes).values( |
57 | | - OwnerId=owner_id, SEName=sb_SE, SEPFN=se_PFN, Bytes=size |
| 58 | + OwnerId=owner_id, SEName=SE_NAME, SEPFN=pfn, Bytes=size |
58 | 59 | ) |
59 | 60 | try: |
60 | 61 | result = await self.conn.execute(stmt) |
61 | | - return result.lastrowid |
62 | 62 | except sqlalchemy.exc.IntegrityError: |
63 | | - # it is a duplicate, try to retrieve SBiD |
64 | | - stmt: sqlalchemy.Executable = sqlalchemy.select(sb_SandBoxes.SBId).where( # type: ignore[no-redef] |
65 | | - sb_SandBoxes.SEPFN == se_PFN, |
66 | | - sb_SandBoxes.SEName == sb_SE, |
67 | | - sb_SandBoxes.OwnerId == owner_id, |
68 | | - ) |
69 | | - result = await self.conn.execute(stmt) |
70 | | - sb_ID = result.scalar_one() |
71 | | - stmt: sqlalchemy.Executable = ( # type: ignore[no-redef] |
72 | | - sqlalchemy.update(sb_SandBoxes) |
73 | | - .where(sb_SandBoxes.SBId == sb_ID) |
74 | | - .values(LastAccessTime=datetime.datetime.utcnow()) |
75 | | - ) |
76 | | - await self.conn.execute(stmt) |
77 | | - return sb_ID |
78 | | - |
79 | | - async def exists_and_assigned(self, name: str) -> bool: |
80 | | - """Checks if a sandbox exists and has been assigned |
| 63 | + await self.update_sandbox_last_access_time(pfn) |
| 64 | + else: |
| 65 | + assert result.rowcount == 1 |
| 66 | + |
| 67 | + async def update_sandbox_last_access_time(self, pfn: str) -> None: |
| 68 | + stmt = ( |
| 69 | + sqlalchemy.update(sb_SandBoxes) |
| 70 | + .where(sb_SandBoxes.SEName == SE_NAME, sb_SandBoxes.SEPFN == pfn) |
| 71 | + .values(LastAccessTime=utcnow()) |
| 72 | + ) |
| 73 | + result = await self.conn.execute(stmt) |
| 74 | + assert result.rowcount == 1 |
81 | 75 |
|
82 | | - As sandboxes are registered in the DB before uploading to the storage |
83 | | - backend we can't on their existence in the database to determine if |
84 | | - they have been uploaded. Instead we check if the sandbox has been |
85 | | - assigned to a job. If it has then we know it has been uploaded and we |
86 | | - can avoid communicating with the storage backend. |
87 | | - """ |
| 76 | + async def sandbox_is_assigned(self, pfn: str) -> bool: |
| 77 | + """Checks if a sandbox exists and has been assigned.""" |
88 | 78 | stmt: sqlalchemy.Executable = sqlalchemy.select(sb_SandBoxes.Assigned).where( |
89 | | - sb_SandBoxes.SEName == SE_NAME, |
90 | | - sb_SandBoxes.SEPFN == name, |
| 79 | + sb_SandBoxes.SEName == SE_NAME, sb_SandBoxes.SEPFN == pfn |
91 | 80 | ) |
92 | 81 | result = await self.conn.execute(stmt) |
93 | | - return result.scalar_one() |
| 82 | + is_assigned = result.scalar_one() |
| 83 | + return is_assigned |
94 | 84 |
|
95 | 85 | async def delete(self, sandbox_ids: list[int]) -> bool: |
96 | 86 | stmt: sqlalchemy.Executable = sqlalchemy.delete(sb_SandBoxes).where( |
|
0 commit comments