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_DB_PVC_SUFFIX ,
19+ get_autoscaler_vm_identity ,
20+ )
1221from ..deployment .kubernetes .snapshot import (
1322 create_snapshot_from_pvc ,
1423 ensure_snapshot_absent ,
2029if TYPE_CHECKING :
2130 from ulid import ULID
2231
23- logger = logging . getLogger ( __name__ )
32+ from .. models . backups import BackupEntry
2433
25- SNAPSHOT_TIMEOUT_SEC = int (os .environ .get ("SNAPSHOT_TIMEOUT_SEC" , "120" ))
26- SNAPSHOT_POLL_INTERVAL_SEC = int (os .environ .get ("SNAPSHOT_POLL_INTERVAL_SEC" , "5" ))
34+ logger = logging .getLogger (__name__ )
2735
2836_K8S_NAME_MAX_LENGTH = 63
37+ DEFAULT_SNAPSHOT_TIMEOUT_SEC = float (SNAPSHOT_TIMEOUT_SEC )
38+ DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC = float (SNAPSHOT_POLL_INTERVAL_SEC )
39+
40+
41+ class SnapshotMetadata (BaseModel ):
42+ name : str = Field (..., min_length = 1 )
43+ namespace : str = Field (..., min_length = 1 )
44+ # content_name stays optional because there are runtime scenarios where the
45+ # VolumeSnapshotContent hasn’t been bound yet
46+ content_name : str | None
47+
48+
49+ def build_snapshot_metadata (backup : BackupEntry ) -> SnapshotMetadata | None :
50+ name = backup .snapshot_name
51+ namespace = backup .snapshot_namespace
52+ if not name or not namespace :
53+ logger .debug (
54+ "Skipping metadata for missing snapshot identifiers (name=%r namespace=%r)" ,
55+ name ,
56+ namespace ,
57+ )
58+ return None
59+ return SnapshotMetadata (
60+ name = name ,
61+ namespace = namespace ,
62+ content_name = backup .snapshot_content_name ,
63+ )
2964
3065
3166@dataclass (frozen = True )
@@ -58,20 +93,18 @@ def _build_snapshot_name(*, label: str, backup_id: ULID) -> str:
5893 return f"{ label_component } { separator } { backup_component } "
5994
6095
61- async def create_branch_snapshot (
62- branch_id : Identifier ,
96+ async def _create_snapshot_from_pvc (
6397 * ,
98+ namespace : str ,
99+ pvc_name : str ,
64100 backup_id : ULID ,
65101 snapshot_class : str ,
66- poll_interval : float ,
67102 label : str ,
103+ poll_interval : float ,
68104 time_limit : float ,
69105) -> SnapshotDetails :
70- namespace , autoscaler_vm_name = get_autoscaler_vm_identity (branch_id )
71- pvc_name = f"{ autoscaler_vm_name } { AUTOSCALER_PVC_SUFFIX } "
72106 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 )
107+ logger .info ("Creating VolumeSnapshot %s/%s for branch PVC %s" , namespace , snapshot_name , pvc_name )
75108 try :
76109 async with asyncio .timeout (time_limit ):
77110 await create_snapshot_from_pvc (
@@ -88,10 +121,10 @@ async def create_branch_snapshot(
88121 )
89122 except TimeoutError :
90123 logger .exception (
91- "Timed out creating VolumeSnapshot %s/%s for branch %s within %s seconds" ,
124+ "Timed out creating VolumeSnapshot %s/%s for PVC %s within %s seconds" ,
92125 namespace ,
93126 snapshot_name ,
94- branch_id ,
127+ pvc_name ,
95128 time_limit ,
96129 )
97130 raise
@@ -115,29 +148,43 @@ async def create_branch_snapshot(
115148 )
116149
117150
118- async def delete_branch_snapshot (
151+ async def create_branch_db_snapshot (
152+ branch_id : Identifier ,
119153 * ,
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
154+ backup_id : ULID ,
155+ snapshot_class : str = VOLUME_SNAPSHOT_CLASS ,
156+ poll_interval : float = DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC ,
157+ label : str ,
158+ time_limit : float = DEFAULT_SNAPSHOT_TIMEOUT_SEC ,
159+ ) -> SnapshotDetails :
160+ namespace , autoscaler_vm_name = get_autoscaler_vm_identity (branch_id )
161+ pvc_name = f"{ autoscaler_vm_name } { AUTOSCALER_DB_PVC_SUFFIX } "
162+ return await _create_snapshot_from_pvc (
163+ namespace = namespace ,
164+ pvc_name = pvc_name ,
165+ backup_id = backup_id ,
166+ snapshot_class = snapshot_class ,
167+ poll_interval = poll_interval ,
168+ label = label ,
169+ time_limit = time_limit ,
170+ )
171+
133172
134- derived_content_name = content_name
173+ async def delete_snapshot (
174+ metadata : SnapshotMetadata ,
175+ * ,
176+ time_limit : float = DEFAULT_SNAPSHOT_TIMEOUT_SEC ,
177+ poll_interval : float = DEFAULT_SNAPSHOT_POLL_INTERVAL_SEC ,
178+ ) -> None :
179+ name = metadata .name
180+ namespace = metadata .namespace
181+ content_name = metadata .content_name
135182 try :
136183 async with asyncio .timeout (time_limit ):
137184 snapshot = await read_snapshot (namespace , name )
138185 if snapshot is not None :
139186 status = snapshot .get ("status" ) or {}
140- derived_content_name = derived_content_name or status .get ("boundVolumeSnapshotContentName" )
187+ content_name = content_name or status .get ("boundVolumeSnapshotContentName" )
141188 logger .info ("Deleting VolumeSnapshot %s/%s" , namespace , name )
142189 await ensure_snapshot_absent (
143190 namespace ,
@@ -148,10 +195,10 @@ async def delete_branch_snapshot(
148195 else :
149196 logger .info ("VolumeSnapshot %s/%s already absent" , namespace , name )
150197
151- if derived_content_name :
152- logger .info ("Ensuring VolumeSnapshotContent %s is absent" , derived_content_name )
198+ if content_name :
199+ logger .info ("Ensuring VolumeSnapshotContent %s is absent" , content_name )
153200 await ensure_snapshot_content_absent (
154- derived_content_name ,
201+ content_name ,
155202 timeout = time_limit ,
156203 poll_interval = poll_interval ,
157204 )
0 commit comments