Skip to content

Commit f5e5bfc

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 b67f4ab commit f5e5bfc

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
@@ -823,7 +823,9 @@ def is_master(self):
823823

824824
@override
825825
def check_sr(self, sr_uuid) -> None:
826-
self.database_backup("auto", delay=3600)
826+
# Automatic backup if there were no backups for the last hour.
827+
# Let it fail if needed, so full Traceback is on SMLog.
828+
self.database_backup("auto", delay=3600, fail=True)
827829

828830

829831
@override
@@ -1572,10 +1574,22 @@ def _kick_gc(self):
15721574
util.SMlog('Kicking GC')
15731575
cleanup.start_gc_service(self.uuid)
15741576

1575-
def database_backup(self, name="", *, delay=0):
1577+
def database_backup(self, name="", *, delay=0, fail=False):
1578+
"""Generate a new database backup file.
1579+
This operation should not prevent the underlying action to be successful.
1580+
Hence all Exceptions are caught and re-raised only if asked to.
1581+
"""
15761582
if not self._linstor:
15771583
self._reconnect()
1578-
self._linstor.database_backup(name, delay=delay)
1584+
try:
1585+
self._linstor.database_backup(name, delay=delay)
1586+
except Exception as e:
1587+
util.SMlog(
1588+
"[database_backup] Error during creation: {}".format(e),
1589+
priority=util.LOG_ERR,
1590+
)
1591+
if fail:
1592+
raise
15791593

15801594
# ==============================================================================
15811595
# 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

@@ -1769,32 +1767,19 @@ def get_database_path(self):
17691767

17701768
def database_backup(self, name="", *, delay=0):
17711769
now = datetime.now()
1772-
# Throttling to avoid too many backups of the same kind on a short period
1770+
# Throttling to avoid too many backups on a short period
17731771
if delay:
1774-
_, date_latest = self._get_latest_database_backup(name)
1775-
if date_latest and ((now - date_latest).total_seconds() < delay):
1772+
latest = datetime.strptime(
1773+
self._get_latest_logged_database_backup_date(),
1774+
DATABASE_BACKUP_DATE_FORMAT)
1775+
if (now - latest).total_seconds() < delay:
17761776
return # No backup for now
1777-
1778-
# Create new backup with link to latest
1779-
filename = DATABASE_BACKUP_NAME_FORMAT.format(now.strftime(DATABASE_BACKUP_DATE_FORMAT), name)
1777+
# Create new backup
1778+
date = now.strftime(DATABASE_BACKUP_DATE_FORMAT)
1779+
filename = DATABASE_BACKUP_NAME_FORMAT.format(date, name)
17801780
self._linstor.controller_backupdb(filename)
1781-
# Copy to secondary backup location
1782-
with contextlib.suppress(OSError):
1783-
os.makedirs(DATABASE_BACKUP_DIR_SPARE, mode=0o755, exist_ok=True)
1784-
shutil.copy2(
1785-
(DATABASE_BACKUP_DIR_MAIN / filename).with_suffix(".zip"),
1786-
DATABASE_BACKUP_DIR_SPARE,
1787-
)
1788-
for directory in (DATABASE_BACKUP_DIR_MAIN, DATABASE_BACKUP_DIR_SPARE):
1789-
# Remove and set latest
1790-
with contextlib.suppress(OSError):
1791-
(directory / DATABASE_BACKUP_NAME_LATEST).unlink()
1792-
os.link(str((directory / filename).with_suffix(".zip")),
1793-
str((directory / DATABASE_BACKUP_NAME_LATEST)))
1794-
# Apply retention
1795-
for old_file, _ in self._get_sorted_database_backup(directory)[DATABASE_BACKUP_RETENTION:]:
1796-
os.unlink(old_file)
1797-
util.SMlog("[database_backup] Created: {}".format(filename))
1781+
self._log_database_backup(date, name)
1782+
util.SMlog("[database_backup] Created: {}".format(filename), priority=util.LOG_INFO)
17981783

17991784
@classmethod
18001785
def get_all_group_names(cls, base_name):
@@ -2652,23 +2637,23 @@ def _get_volume_properties(self, volume_uuid):
26522637
properties.namespace = self._build_volume_namespace(volume_uuid)
26532638
return properties
26542639

2655-
def _list_database_backup(self, database_backup_dir, name="*"):
2656-
for path in database_backup_dir.glob(DATABASE_BACKUP_NAME_FORMAT.format(
2657-
"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"):
2658-
try:
2659-
yield path, datetime.strptime(path.name.split("-")[1], DATABASE_BACKUP_DATE_FORMAT)
2660-
except (ValueError, IndexError):
2661-
continue
2640+
def _log_database_backup(self, date, name):
2641+
"""Log a database backup operation: "date name"
2642+
We cannot assume the pool-master is the same as the linstor-master,
2643+
this file is on the pool-master, and serves for the throttling."""
2644+
os.makedirs(DATABASE_BACKUP_LOGDIR, mode=0o755, exist_ok=True)
2645+
with open(DATABASE_BACKUP_LOGFILE, "a", encoding="utf8") as f:
2646+
f.write(f"{date} {name}")
26622647

2663-
def _get_sorted_database_backup(self, database_backup_dir, name="*"):
2664-
return sorted(self._list_database_backup(database_backup_dir, name),
2665-
reverse=True,
2666-
key=lambda p: p[0].stat().st_mtime)
2667-
2668-
def _get_latest_database_backup(self, name="*"):
2669-
return max(self._list_database_backup(DATABASE_BACKUP_DIR_MAIN, name),
2670-
default=(None, None),
2671-
key=lambda p: p[0].stat().st_mtime)
2648+
def _get_latest_logged_database_backup_date(self):
2649+
# get last log line if it exists, and return the corresponding date
2650+
try:
2651+
with open(DATABASE_BACKUP_LOGFILE, "rb") as f:
2652+
# seek from the end, with 256 as a most-probable maximum line length
2653+
f.seek(-min(os.stat(DATABASE_BACKUP_LOGFILE).st_size, 256), os.SEEK_END)
2654+
return f.read().decode().splitlines()[-1].split()[0]
2655+
except FileNotFoundError:
2656+
return datetime.utcfromtimestamp(0)
26722657

26732658
@classmethod
26742659
def _build_sr_namespace(cls):

0 commit comments

Comments
 (0)