|
44 | 44 | DATABASE_SIZE = 1 << 30 # 1GB. |
45 | 45 | DATABASE_PATH = '/var/lib/linstor' |
46 | 46 | 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" |
49 | 49 | DATABASE_BACKUP_NAME_FORMAT = "linstor_database_backup-{}-{}" |
50 | | -DATABASE_BACKUP_NAME_LATEST = "linstor_database_backup-latest.zip" |
51 | | -DATABASE_BACKUP_RETENTION = 10 |
52 | 50 | DATABASE_BACKUP_DATE_FORMAT = "%Y%m%d_%H%M%S" |
53 | 51 | LINSTOR_SATELLITE_PORT = 3366 |
54 | 52 |
|
@@ -1768,32 +1766,19 @@ def get_database_path(self): |
1768 | 1766 |
|
1769 | 1767 | def database_backup(self, name="", *, delay=0): |
1770 | 1768 | 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 |
1772 | 1770 | 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: |
1775 | 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) |
| 1776 | + # Create new backup |
| 1777 | + date = now.strftime(DATABASE_BACKUP_DATE_FORMAT) |
| 1778 | + filename = DATABASE_BACKUP_NAME_FORMAT.format(date, name) |
1779 | 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)) |
| 1780 | + self._log_database_backup(date, name) |
| 1781 | + util.SMlog("[database_backup] Created: {}".format(filename), priority=util.LOG_INFO) |
1797 | 1782 |
|
1798 | 1783 | @classmethod |
1799 | 1784 | def get_all_group_names(cls, base_name): |
@@ -2651,23 +2636,23 @@ def _get_volume_properties(self, volume_uuid): |
2651 | 2636 | properties.namespace = self._build_volume_namespace(volume_uuid) |
2652 | 2637 | return properties |
2653 | 2638 |
|
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}") |
2661 | 2646 |
|
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) |
2671 | 2656 |
|
2672 | 2657 | @classmethod |
2673 | 2658 | def _build_sr_namespace(cls): |
|
0 commit comments