Skip to content

Commit 0e5dcb8

Browse files
committed
Linstor: (fix) Remove all operations on backup files since we cannot always access them from the pool master.
- Backup are still being done through the Linstor API - Throttling is enforced using another method. - No retention is applied, no verifications of backup files are being made. The removed operations needs to go somewhere where they'll be executed on the Linstor Master. All SMlog have proper priority. Failure (letting Exceptions be raised) only happens on non-interactive operations, preventing actions like snapshot from failing on an unreleted task, since the operation itself is finished and was successful. Signed-off-by: Arnaud Garcia-Fernandez <arnaud.garcia-fernandez@vates.tech>
1 parent 1dfb701 commit 0e5dcb8

2 files changed

Lines changed: 45 additions & 46 deletions

File tree

drivers/LinstorSR.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,9 @@ def is_master(self):
811811

812812
@override
813813
def check_sr(self, sr_uuid) -> None:
814-
self.database_backup("auto", delay=3600)
814+
# Automatic backup if there were no backups for the last hour.
815+
# Let it fail if needed, so full Traceback is on SMLog.
816+
self.database_backup("auto", delay=3600, fail=True)
815817

816818

817819
@override
@@ -1563,10 +1565,22 @@ def _kick_gc(self):
15631565
util.SMlog('Kicking GC')
15641566
cleanup.start_gc_service(self.uuid)
15651567

1566-
def database_backup(self, name="", *, delay=0):
1568+
def database_backup(self, name="", *, delay=0, fail=False):
1569+
"""Generate a new database backup file.
1570+
This operation should not prevent the underlying action to be successful.
1571+
Hence all Exceptions are caught and re-raised only if asked to.
1572+
"""
15671573
if not self._linstor:
15681574
self._reconnect()
1569-
self._linstor.database_backup(name, delay=delay)
1575+
try:
1576+
self._linstor.database_backup(name, delay=delay)
1577+
except Exception as e:
1578+
util.SMlog(
1579+
"[database_backup] Error during creation: {}".format(e),
1580+
priority=util.LOG_ERR,
1581+
)
1582+
if fail:
1583+
raise
15701584

15711585
# ==============================================================================
15721586
# LinstorSr VDI

drivers/linstorvolumemanager.py

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,9 @@
4444
DATABASE_SIZE = 1 << 30 # 1GB.
4545
DATABASE_PATH = '/var/lib/linstor'
4646
DATABASE_MKFS = 'mkfs.ext4'
47-
DATABASE_BACKUP_DIR_MAIN = Path(DATABASE_PATH)
48-
DATABASE_BACKUP_DIR_SPARE = Path('/var/lib/linstor.d/db-backups')
47+
DATABASE_BACKUP_LOGDIR = Path('/var/lib/linstor.d/db-backups')
48+
DATABASE_BACKUP_LOGFILE = DATABASE_BACKUP_LOGDIR / "log.txt"
4949
DATABASE_BACKUP_NAME_FORMAT = "linstor_database_backup-{}-{}"
50-
DATABASE_BACKUP_NAME_LATEST = "linstor_database_backup-latest.zip"
51-
DATABASE_BACKUP_RETENTION = 10
5250
DATABASE_BACKUP_DATE_FORMAT = "%Y%m%d_%H%M%S"
5351
LINSTOR_SATELLITE_PORT = 3366
5452

@@ -1768,32 +1766,19 @@ def get_database_path(self):
17681766

17691767
def database_backup(self, name="", *, delay=0):
17701768
now = datetime.now()
1771-
# Throttling to avoid too many backups of the same kind on a short period
1769+
# Throttling to avoid too many backups on a short period
17721770
if delay:
1773-
_, date_latest = self._get_latest_database_backup(name)
1774-
if date_latest and ((now - date_latest).total_seconds() < delay):
1771+
latest = datetime.strptime(
1772+
self._get_latest_logged_database_backup_date(),
1773+
DATABASE_BACKUP_DATE_FORMAT)
1774+
if (now - latest).total_seconds() < delay:
17751775
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)
1776+
# Create new backup
1777+
date = now.strftime(DATABASE_BACKUP_DATE_FORMAT)
1778+
filename = DATABASE_BACKUP_NAME_FORMAT.format(date, name)
17791779
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))
1780+
self._log_database_backup(date, name)
1781+
util.SMlog("[database_backup] Created: {}".format(filename), priority=util.LOG_INFO)
17971782

17981783
@classmethod
17991784
def get_all_group_names(cls, base_name):
@@ -2651,23 +2636,23 @@ def _get_volume_properties(self, volume_uuid):
26512636
properties.namespace = self._build_volume_namespace(volume_uuid)
26522637
return properties
26532638

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
2639+
def _log_database_backup(self, date, name):
2640+
"""Log a database backup operation: "date name"
2641+
We cannot assume the pool-master is the same as the linstor-master,
2642+
this file is on the pool-master, and serves for the throttling."""
2643+
os.makedirs(DATABASE_BACKUP_LOGDIR, mode=0o755, exist_ok=True)
2644+
with open(DATABASE_BACKUP_LOGFILE, "a", encoding="utf8") as f:
2645+
f.write(f"{date} {name}")
26612646

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)
2647+
def _get_latest_logged_database_backup_date(self):
2648+
# get last log line if it exists, and return the corresponding date
2649+
try:
2650+
with open(DATABASE_BACKUP_LOGFILE, "rb") as f:
2651+
# seek from the end, with 256 as a most-probable maximum line length
2652+
f.seek(-min(os.stat(DATABASE_BACKUP_LOGFILE).st_size, 256), os.SEEK_END)
2653+
return f.read().decode().splitlines()[-1].split()[0]
2654+
except FileNotFoundError:
2655+
return datetime.utcfromtimestamp(0)
26712656

26722657
@classmethod
26732658
def _build_sr_namespace(cls):

0 commit comments

Comments
 (0)