|
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 |
|
@@ -1769,32 +1767,19 @@ def get_database_path(self): |
1769 | 1767 |
|
1770 | 1768 | def database_backup(self, name="", *, delay=0): |
1771 | 1769 | 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 |
1773 | 1771 | 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: |
1776 | 1776 | 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) |
1780 | 1780 | 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) |
1798 | 1783 |
|
1799 | 1784 | @classmethod |
1800 | 1785 | def get_all_group_names(cls, base_name): |
@@ -2652,23 +2637,23 @@ def _get_volume_properties(self, volume_uuid): |
2652 | 2637 | properties.namespace = self._build_volume_namespace(volume_uuid) |
2653 | 2638 | return properties |
2654 | 2639 |
|
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}") |
2662 | 2647 |
|
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) |
2672 | 2657 |
|
2673 | 2658 | @classmethod |
2674 | 2659 | def _build_sr_namespace(cls): |
|
0 commit comments