Skip to content

Commit 6036431

Browse files
YthogthaWescoeur
authored andcommitted
feat(linstor): backup on major ops, and regularly on check_sr
- Retention of 10 backups from operations, and check_sr always saves on latest. - Provides a secondary backup location, with the same retention policy. Signed-off-by: Arnaud Garcia-Fernandez <arnaud.garcia-fernandez@vates.tech>
1 parent 2473186 commit 6036431

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

drivers/LinstorSR.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,11 @@ def is_master(self):
809809

810810
return self._is_master
811811

812+
@override
813+
def check_sr(self, sr_uuid) -> None:
814+
self.database_backup("auto", delay=3600)
815+
816+
812817
@override
813818
@_locked_load
814819
def vdi(self, uuid) -> VDI.VDI:
@@ -1558,6 +1563,11 @@ def _kick_gc(self):
15581563
util.SMlog('Kicking GC')
15591564
cleanup.start_gc_service(self.uuid)
15601565

1566+
def database_backup(self, name="", *, delay=0):
1567+
if not self._linstor:
1568+
self._reconnect()
1569+
self._linstor.database_backup(name, delay=delay)
1570+
15611571
# ==============================================================================
15621572
# LinstorSr VDI
15631573
# ==============================================================================
@@ -1757,6 +1767,8 @@ def create(self, sr_uuid, vdi_uuid, size) -> str:
17571767
self.ref = self._db_introduce()
17581768
self.sr._update_stats(self.size)
17591769

1770+
self.sr.database_backup("create")
1771+
17601772
return VDI.VDI.get_params(self)
17611773

17621774
@override
@@ -1804,7 +1816,8 @@ def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None:
18041816
# TODO: Check size after delete.
18051817
self.sr._update_stats(-self.size)
18061818
self.sr._kick_gc()
1807-
return super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1819+
super(LinstorVDI, self).delete(sr_uuid, vdi_uuid, data_only)
1820+
self.sr.database_backup("delete")
18081821

18091822
@override
18101823
def attach(self, sr_uuid, vdi_uuid) -> str:
@@ -2379,6 +2392,7 @@ def _do_snapshot(self, sr_uuid, vdi_uuid, snapType,
23792392
finally:
23802393
self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary)
23812394
blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary)
2395+
self.sr.database_backup("snapshot")
23822396

23832397
def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None):
23842398
util.SMlog(

drivers/linstorvolumemanager.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
import time
3333
import util
3434
import uuid
35+
from datetime import datetime
36+
from pathlib import Path
37+
import contextlib
3538

3639
# Persistent prefix to add to RAW persistent volumes.
3740
PERSISTENT_PREFIX = 'xcp-persistent-'
@@ -41,7 +44,12 @@
4144
DATABASE_SIZE = 1 << 30 # 1GB.
4245
DATABASE_PATH = '/var/lib/linstor'
4346
DATABASE_MKFS = 'mkfs.ext4'
44-
47+
DATABASE_BACKUP_DIR_MAIN = Path(DATABASE_PATH)
48+
DATABASE_BACKUP_DIR_SPARE = Path('/var/lib/linstor.d/db-backups')
49+
DATABASE_BACKUP_NAME_FORMAT = "linstor_database_backup-{}-{}"
50+
DATABASE_BACKUP_NAME_LATEST = "linstor_database_backup-latest.zip"
51+
DATABASE_BACKUP_RETENTION = 10
52+
DATABASE_BACKUP_DATE_FORMAT = "%Y%m%d_%H%M%S"
4553
LINSTOR_SATELLITE_PORT = 3366
4654

4755
REG_DRBDADM_PRIMARY = re.compile("([^\\s]+)\\s+role:Primary")
@@ -1758,6 +1766,35 @@ def get_database_path(self):
17581766
"""
17591767
return self._request_database_path(self._linstor, activate=True)
17601768

1769+
def database_backup(self, name="", *, delay=0):
1770+
now = datetime.now()
1771+
# Throttling to avoid too many backups of the same kind on a short period
1772+
if delay:
1773+
_, date_latest = self._get_latest_database_backup(name)
1774+
if date_latest and ((now - date_latest).total_seconds() < delay):
1775+
return # No backup for now
1776+
1777+
# Create new backup with link to latest
1778+
filename = DATABASE_BACKUP_NAME_FORMAT.format(now.strftime(DATABASE_BACKUP_DATE_FORMAT), name)
1779+
self._linstor.controller_backupdb(filename)
1780+
# Copy to secondary backup location
1781+
with contextlib.suppress(OSError):
1782+
os.makedirs(DATABASE_BACKUP_DIR_SPARE, mode=0o755, exist_ok=True)
1783+
shutil.copy2(
1784+
(DATABASE_BACKUP_DIR_MAIN / filename).with_suffix(".zip"),
1785+
DATABASE_BACKUP_DIR_SPARE,
1786+
)
1787+
for directory in (DATABASE_BACKUP_DIR_MAIN, DATABASE_BACKUP_DIR_SPARE):
1788+
# Remove and set latest
1789+
with contextlib.suppress(OSError):
1790+
(directory / DATABASE_BACKUP_NAME_LATEST).unlink()
1791+
os.link(str((directory / filename).with_suffix(".zip")),
1792+
str((directory / DATABASE_BACKUP_NAME_LATEST)))
1793+
# Apply retention
1794+
for old_file, _ in self._get_sorted_database_backup(directory)[DATABASE_BACKUP_RETENTION:]:
1795+
os.unlink(old_file)
1796+
util.SMlog("[database_backup] Created: {}".format(filename))
1797+
17611798
@classmethod
17621799
def get_all_group_names(cls, base_name):
17631800
"""
@@ -2614,6 +2651,24 @@ def _get_volume_properties(self, volume_uuid):
26142651
properties.namespace = self._build_volume_namespace(volume_uuid)
26152652
return properties
26162653

2654+
def _list_database_backup(self, database_backup_dir, name="*"):
2655+
for path in database_backup_dir.glob(DATABASE_BACKUP_NAME_FORMAT.format(
2656+
"20[0-9][0-9][01][0-9][0-3][0-9]_[0-2][0-9][0-5][0-9][0-5][0-9]", name) + ".zip"):
2657+
try:
2658+
yield path, datetime.strptime(path.name.split("-")[1], DATABASE_BACKUP_DATE_FORMAT)
2659+
except (ValueError, IndexError):
2660+
continue
2661+
2662+
def _get_sorted_database_backup(self, database_backup_dir, name="*"):
2663+
return sorted(self._list_database_backup(database_backup_dir, name),
2664+
reverse=True,
2665+
key=lambda p: p[0].stat().st_mtime)
2666+
2667+
def _get_latest_database_backup(self, name="*"):
2668+
return max(self._list_database_backup(DATABASE_BACKUP_DIR_MAIN, name),
2669+
default=(None, None),
2670+
key=lambda p: p[0].stat().st_mtime)
2671+
26172672
@classmethod
26182673
def _build_sr_namespace(cls):
26192674
return '/{}/'.format(cls.NAMESPACE_SR)

0 commit comments

Comments
 (0)