22
33import asyncio
44import logging
5- import os
65import re
76from dataclasses import dataclass
87from typing import TYPE_CHECKING
98
9+ from pydantic import BaseModel , Field
10+
1011from .._util import Identifier , quantity_to_bytes
11- from ..deployment import AUTOSCALER_PVC_SUFFIX , get_autoscaler_vm_identity
12+ from .._util .backup_config import (
13+ SNAPSHOT_POLL_INTERVAL_SEC ,
14+ SNAPSHOT_TIMEOUT_SEC ,
15+ VOLUME_SNAPSHOT_CLASS ,
16+ )
17+ from ..deployment import (
18+ AUTOSCALER_PVC_SUFFIX ,
19+ get_autoscaler_vm_identity ,
20+ )
1221from ..deployment .kubernetes .snapshot import (
1322 create_snapshot_from_pvc ,
1423 ensure_snapshot_absent ,
2130if TYPE_CHECKING :
2231 from ulid import ULID
2332
24- logger = logging . getLogger ( __name__ )
33+ from .. models . backups import BackupEntry
2534
26- SNAPSHOT_TIMEOUT_SEC = int (os .environ .get ("SNAPSHOT_TIMEOUT_SEC" , "120" ))
27- SNAPSHOT_POLL_INTERVAL_SEC = int (os .environ .get ("SNAPSHOT_POLL_INTERVAL_SEC" , "5" ))
35+ logger = logging .getLogger (__name__ )
2836
2937_K8S_NAME_MAX_LENGTH = 63
38+ DEFAULT_SNAPSHOT_TIMEOUT_SEC = float (SNAPSHOT_TIMEOUT_SEC )
39+ DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC = float (SNAPSHOT_POLL_INTERVAL_SEC )
40+
41+
42+ class SnapshotMetadata (BaseModel ):
43+ name : str = Field (..., min_length = 1 )
44+ namespace : str = Field (..., min_length = 1 )
45+ # content_name stays optional because there are runtime scenarios where the
46+ # VolumeSnapshotContent hasn’t been bound yet
47+ content_name : str | None
48+
49+
50+ def build_snapshot_metadata (backup : BackupEntry ) -> SnapshotMetadata | None :
51+ name = backup .snapshot_name
52+ namespace = backup .snapshot_namespace
53+ if not name or not namespace :
54+ logger .debug (
55+ "Skipping metadata for missing snapshot identifiers (name=%r namespace=%r)" ,
56+ name ,
57+ namespace ,
58+ )
59+ return None
60+ return SnapshotMetadata (
61+ name = name ,
62+ namespace = namespace ,
63+ content_name = backup .snapshot_content_name ,
64+ )
3065
3166
3267@dataclass (frozen = True )
@@ -59,20 +94,18 @@ def _build_snapshot_name(*, label: str, backup_id: ULID) -> str:
5994 return f"{ label_component } { separator } { backup_component } "
6095
6196
62- async def create_branch_snapshot (
63- branch_id : Identifier ,
97+ async def _create_snapshot_from_pvc (
6498 * ,
99+ namespace : str ,
100+ pvc_name : str ,
65101 backup_id : ULID ,
66102 snapshot_class : str ,
67- poll_interval : float ,
68103 label : str ,
104+ poll_interval : float ,
69105 time_limit : float ,
70106) -> SnapshotDetails :
71- namespace , autoscaler_vm_name = get_autoscaler_vm_identity (branch_id )
72- pvc_name = f"{ autoscaler_vm_name } { AUTOSCALER_PVC_SUFFIX } "
73107 snapshot_name = _build_snapshot_name (label = label , backup_id = backup_id )
74-
75- logger .info ("Creating VolumeSnapshot %s/%s for branch %s" , namespace , snapshot_name , branch_id )
108+ logger .info ("Creating VolumeSnapshot %s/%s for branch PVC %s" , namespace , snapshot_name , pvc_name )
76109 try :
77110 async with asyncio .timeout (time_limit ):
78111 await create_snapshot_from_pvc (
@@ -89,14 +122,14 @@ async def create_branch_snapshot(
89122 )
90123 except TimeoutError as exc :
91124 logger .exception (
92- "Timed out creating VolumeSnapshot %s/%s for branch %s within %s seconds" ,
125+ "Timed out creating VolumeSnapshot %s/%s for PVC %s within %s seconds" ,
93126 namespace ,
94127 snapshot_name ,
95- branch_id ,
128+ pvc_name ,
96129 time_limit ,
97130 )
98131 raise VelaSnapshotTimeoutError (
99- f"Timed out creating VolumeSnapshot { namespace } /{ snapshot_name } for branch { branch_id } "
132+ f"Timed out creating VolumeSnapshot { namespace } /{ snapshot_name } for namespace { namespace } "
100133 ) from exc
101134
102135 status = snapshot .get ("status" ) or {}
@@ -118,29 +151,43 @@ async def create_branch_snapshot(
118151 )
119152
120153
121- async def delete_branch_snapshot (
154+ async def create_branch_db_snapshot (
155+ branch_id : Identifier ,
122156 * ,
123- name : str | None ,
124- namespace : str | None ,
125- content_name : str | None ,
126- time_limit : float = SNAPSHOT_TIMEOUT_SEC ,
127- poll_interval : float = SNAPSHOT_POLL_INTERVAL_SEC ,
128- ) -> None :
129- if not name or not namespace :
130- logger .debug (
131- "Skipping deletion for VolumeSnapshot with missing metadata (name=%s namespace=%s)" ,
132- name ,
133- namespace ,
134- )
135- return
157+ backup_id : ULID ,
158+ snapshot_class : str = VOLUME_SNAPSHOT_CLASS ,
159+ poll_interval : float = DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC ,
160+ label : str ,
161+ time_limit : float = DEFAULT_SNAPSHOT_TIMEOUT_SEC ,
162+ ) -> SnapshotDetails :
163+ namespace , autoscaler_vm_name = get_autoscaler_vm_identity (branch_id )
164+ pvc_name = f"{ autoscaler_vm_name } { AUTOSCALER_PVC_SUFFIX } "
165+ return await _create_snapshot_from_pvc (
166+ namespace = namespace ,
167+ pvc_name = pvc_name ,
168+ backup_id = backup_id ,
169+ snapshot_class = snapshot_class ,
170+ poll_interval = poll_interval ,
171+ label = label ,
172+ time_limit = time_limit ,
173+ )
174+
136175
137- derived_content_name = content_name
176+ async def delete_snapshot (
177+ metadata : SnapshotMetadata ,
178+ * ,
179+ time_limit : float = DEFAULT_SNAPSHOT_TIMEOUT_SEC ,
180+ poll_interval : float = DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC ,
181+ ) -> None :
182+ name = metadata .name
183+ namespace = metadata .namespace
184+ content_name = metadata .content_name
138185 try :
139186 async with asyncio .timeout (time_limit ):
140187 snapshot = await read_snapshot (namespace , name )
141188 if snapshot is not None :
142189 status = snapshot .get ("status" ) or {}
143- derived_content_name = derived_content_name or status .get ("boundVolumeSnapshotContentName" )
190+ content_name = content_name or status .get ("boundVolumeSnapshotContentName" )
144191 logger .info ("Deleting VolumeSnapshot %s/%s" , namespace , name )
145192 await ensure_snapshot_absent (
146193 namespace ,
@@ -151,10 +198,10 @@ async def delete_branch_snapshot(
151198 else :
152199 logger .info ("VolumeSnapshot %s/%s already absent" , namespace , name )
153200
154- if derived_content_name :
155- logger .info ("Ensuring VolumeSnapshotContent %s is absent" , derived_content_name )
201+ if content_name :
202+ logger .info ("Ensuring VolumeSnapshotContent %s is absent" , content_name )
156203 await ensure_snapshot_content_absent (
157- derived_content_name ,
204+ content_name ,
158205 timeout = time_limit ,
159206 poll_interval = poll_interval ,
160207 )
0 commit comments