|
32 | 32 | import time |
33 | 33 | import util |
34 | 34 | import uuid |
| 35 | +from datetime import datetime |
| 36 | +from pathlib import Path |
| 37 | +import contextlib |
35 | 38 |
|
36 | 39 | # Persistent prefix to add to RAW persistent volumes. |
37 | 40 | PERSISTENT_PREFIX = 'xcp-persistent-' |
|
41 | 44 | DATABASE_SIZE = 1 << 30 # 1GB. |
42 | 45 | DATABASE_PATH = '/var/lib/linstor' |
43 | 46 | 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" |
45 | 53 | LINSTOR_SATELLITE_PORT = 3366 |
46 | 54 |
|
47 | 55 | REG_DRBDADM_PRIMARY = re.compile("([^\\s]+)\\s+role:Primary") |
@@ -1758,6 +1766,35 @@ def get_database_path(self): |
1758 | 1766 | """ |
1759 | 1767 | return self._request_database_path(self._linstor, activate=True) |
1760 | 1768 |
|
| 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 | + |
1761 | 1798 | @classmethod |
1762 | 1799 | def get_all_group_names(cls, base_name): |
1763 | 1800 | """ |
@@ -2614,6 +2651,24 @@ def _get_volume_properties(self, volume_uuid): |
2614 | 2651 | properties.namespace = self._build_volume_namespace(volume_uuid) |
2615 | 2652 | return properties |
2616 | 2653 |
|
| 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 | + |
2617 | 2672 | @classmethod |
2618 | 2673 | def _build_sr_namespace(cls): |
2619 | 2674 | return '/{}/'.format(cls.NAMESPACE_SR) |
|
0 commit comments