77from dataclasses import dataclass
88from typing import TYPE_CHECKING
99
10+ from pydantic import BaseModel , Field
11+
1012from .._util import Identifier , quantity_to_bytes
11- from ..deployment import AUTOSCALER_PVC_SUFFIX , get_autoscaler_vm_identity
13+ from .._util .backup_config import (
14+ SNAPSHOT_POLL_INTERVAL_SEC ,
15+ SNAPSHOT_TIMEOUT_SEC ,
16+ VOLUME_SNAPSHOT_CLASS ,
17+ )
18+ from ..deployment import (
19+ AUTOSCALER_DB_PVC_SUFFIX ,
20+ get_autoscaler_vm_identity ,
21+ )
1222from ..deployment .kubernetes .snapshot import (
1323 create_snapshot_from_pvc ,
1424 ensure_snapshot_absent ,
1828)
1929
2030if TYPE_CHECKING :
31+
2132 from ulid import ULID
2233
34+ from ..models .backups import BackupEntry
35+
2336logger = logging .getLogger (__name__ )
2437
2538SNAPSHOT_TIMEOUT_SEC = int (os .environ .get ("SNAPSHOT_TIMEOUT_SEC" , "120" ))
2639SNAPSHOT_POLL_INTERVAL_SEC = int (os .environ .get ("SNAPSHOT_POLL_INTERVAL_SEC" , "5" ))
2740
2841_K8S_NAME_MAX_LENGTH = 63
42+ DEFAULT_SNAPSHOT_TIMEOUT_SEC = float (SNAPSHOT_TIMEOUT_SEC )
43+ DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC = float (SNAPSHOT_POLL_INTERVAL_SEC )
44+
45+
46+ class SnapshotMetadata (BaseModel ):
47+ name : str = Field (..., min_length = 1 )
48+ namespace : str = Field (..., min_length = 1 )
49+ # content_name stays optional because there are runtime scenarios where the
50+ # VolumeSnapshotContent hasn’t been bound yet
51+ content_name : str | None
52+
53+
54+ def build_snapshot_metadata (backup : BackupEntry ) -> SnapshotMetadata | None :
55+ name = backup .snapshot_name
56+ namespace = backup .snapshot_namespace
57+ if not name or not namespace :
58+ logger .debug (
59+ "Skipping metadata for missing snapshot identifiers (name=%r namespace=%r)" ,
60+ name ,
61+ namespace ,
62+ )
63+ return None
64+ return SnapshotMetadata (
65+ name = name ,
66+ namespace = namespace ,
67+ content_name = backup .snapshot_content_name ,
68+ )
2969
3070
3171@dataclass (frozen = True )
@@ -58,20 +98,18 @@ def _build_snapshot_name(*, label: str, backup_id: ULID) -> str:
5898 return f"{ label_component } { separator } { backup_component } "
5999
60100
61- async def create_branch_snapshot (
62- branch_id : Identifier ,
101+ async def _create_snapshot_from_pvc (
63102 * ,
103+ namespace : str ,
104+ pvc_name : str ,
64105 backup_id : ULID ,
65106 snapshot_class : str ,
66- poll_interval : float ,
67107 label : str ,
108+ poll_interval : float ,
68109 time_limit : float ,
69110) -> SnapshotDetails :
70- namespace , autoscaler_vm_name = get_autoscaler_vm_identity (branch_id )
71- pvc_name = f"{ autoscaler_vm_name } { AUTOSCALER_PVC_SUFFIX } "
72111 snapshot_name = _build_snapshot_name (label = label , backup_id = backup_id )
73-
74- logger .info ("Creating VolumeSnapshot %s/%s for branch %s" , namespace , snapshot_name , branch_id )
112+ logger .info ("Creating VolumeSnapshot %s/%s for branch PVC %s" , namespace , snapshot_name , pvc_name )
75113 try :
76114 async with asyncio .timeout (time_limit ):
77115 await create_snapshot_from_pvc (
@@ -88,10 +126,10 @@ async def create_branch_snapshot(
88126 )
89127 except TimeoutError :
90128 logger .exception (
91- "Timed out creating VolumeSnapshot %s/%s for branch %s within %s seconds" ,
129+ "Timed out creating VolumeSnapshot %s/%s for PVC %s within %s seconds" ,
92130 namespace ,
93131 snapshot_name ,
94- branch_id ,
132+ pvc_name ,
95133 time_limit ,
96134 )
97135 raise
@@ -115,29 +153,43 @@ async def create_branch_snapshot(
115153 )
116154
117155
118- async def delete_branch_snapshot (
156+ async def create_branch_db_snapshot (
157+ branch_id : Identifier ,
119158 * ,
120- name : str | None ,
121- namespace : str | None ,
122- content_name : str | None ,
123- time_limit : float = SNAPSHOT_TIMEOUT_SEC ,
124- poll_interval : float = SNAPSHOT_POLL_INTERVAL_SEC ,
125- ) -> None :
126- if not name or not namespace :
127- logger .debug (
128- "Skipping deletion for VolumeSnapshot with missing metadata (name=%s namespace=%s)" ,
129- name ,
130- namespace ,
131- )
132- return
159+ backup_id : ULID ,
160+ snapshot_class : str = VOLUME_SNAPSHOT_CLASS ,
161+ poll_interval : float = DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC ,
162+ label : str ,
163+ time_limit : float = DEFAULT_SNAPSHOT_TIMEOUT_SEC ,
164+ ) -> SnapshotDetails :
165+ namespace , autoscaler_vm_name = get_autoscaler_vm_identity (branch_id )
166+ pvc_name = f"{ autoscaler_vm_name } { AUTOSCALER_DB_PVC_SUFFIX } "
167+ return await _create_snapshot_from_pvc (
168+ namespace = namespace ,
169+ pvc_name = pvc_name ,
170+ backup_id = backup_id ,
171+ snapshot_class = snapshot_class ,
172+ poll_interval = poll_interval ,
173+ label = label ,
174+ time_limit = time_limit ,
175+ )
176+
133177
134- derived_content_name = content_name
178+ async def delete_snapshot (
179+ metadata : SnapshotMetadata ,
180+ * ,
181+ time_limit : float = DEFAULT_SNAPSHOT_TIMEOUT_SEC ,
182+ poll_interval : float = DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC ,
183+ ) -> None :
184+ name = metadata .name
185+ namespace = metadata .namespace
186+ content_name = metadata .content_name
135187 try :
136188 async with asyncio .timeout (time_limit ):
137189 snapshot = await read_snapshot (namespace , name )
138190 if snapshot is not None :
139191 status = snapshot .get ("status" ) or {}
140- derived_content_name = derived_content_name or status .get ("boundVolumeSnapshotContentName" )
192+ content_name = content_name or status .get ("boundVolumeSnapshotContentName" )
141193 logger .info ("Deleting VolumeSnapshot %s/%s" , namespace , name )
142194 await ensure_snapshot_absent (
143195 namespace ,
@@ -148,10 +200,10 @@ async def delete_branch_snapshot(
148200 else :
149201 logger .info ("VolumeSnapshot %s/%s already absent" , namespace , name )
150202
151- if derived_content_name :
152- logger .info ("Ensuring VolumeSnapshotContent %s is absent" , derived_content_name )
203+ if content_name :
204+ logger .info ("Ensuring VolumeSnapshotContent %s is absent" , content_name )
153205 await ensure_snapshot_content_absent (
154- derived_content_name ,
206+ content_name ,
155207 timeout = time_limit ,
156208 poll_interval = poll_interval ,
157209 )
0 commit comments