4545DATABASE_SIZE = 1 << 30 # 1GB.
4646DATABASE_PATH = '/var/lib/linstor'
4747DATABASE_MKFS = 'mkfs.ext4'
48- DATABASE_BACKUP_LOGDIR = Path ('/var/lib/linstor.d/db-backups' )
49- DATABASE_BACKUP_RELATIVE = Path ("../linstor.d/db-backups" )
50- DATABASE_BACKUP_LOGFILE = DATABASE_BACKUP_LOGDIR / "log.txt"
48+ DATABASE_BACKUP_DIR_MAIN = Path (DATABASE_PATH )
49+ DATABASE_BACKUP_DIR_SPARE = Path ("/var/lib/linstor.d/db-backups" )
5150DATABASE_BACKUP_NAME_FORMAT = "linstor_database_backup-{}-{}"
5251DATABASE_BACKUP_RETENTION = 10
5352DATABASE_BACKUP_DATE_FORMAT = "%Y%m%d_%H%M%S"
@@ -1771,43 +1770,48 @@ def get_database_path(self):
17711770 return self ._request_database_path (self ._linstor , activate = True )
17721771
17731772 def is_controller (self ):
1774- """Checks if the current host is the Linstor Controller.
1773+ """
1774+ Checks if the current host is the Linstor Controller.
17751775 This is done by checking that the Linstor database path is a mountpoint.
1776- Which should only be the case on the Linstor Controller."""
1777- return os .path .ismount (DATABASE_PATH )
1778-
1779- def database_backup (self , name = "" , * , delay = 0 ):
1780- now = datetime .now ()
1781- # Throttling to avoid too many backups on a short period
1782- if delay :
1783- latest = datetime .strptime (
1784- self ._get_latest_logged_database_backup_date (),
1785- DATABASE_BACKUP_DATE_FORMAT )
1786- if (now - latest ).total_seconds () < delay :
1787- return # No backup for now
1776+ Which should only be the case on the Linstor Controller.
1777+ """
1778+ return LinstorVolumeManager ._is_mounted (DATABASE_PATH )
1779+
1780+ def database_backup_age (self ):
1781+ """
1782+ Return the latest backup age in seconds.
1783+ If not called on the Controller, since backups are not available,
1784+ returns a huge value (a timestamp of now).
1785+ """
1786+ return (datetime .now () - self ._get_latest_database_backup ()).total_seconds ()
1787+
1788+ def database_backup (self , name = "" ):
17881789 # Create new backup
1789- date = now .strftime (DATABASE_BACKUP_DATE_FORMAT )
1790+ date = datetime . now () .strftime (DATABASE_BACKUP_DATE_FORMAT )
17901791 filename = DATABASE_BACKUP_NAME_FORMAT .format (date , name )
17911792 self ._linstor .controller_backupdb (filename )
17921793 # Relative path are ok for a secondary backup filename:
17931794 # https://github.com/LINBIT/linstor-server/blob/3e9306a9d8215606544c64c50ced150625ee4926/controller/src/main/java/com/linbit/linstor/api/rest/v1/Controller.java#L408
1794- self ._linstor .controller_backupdb (str (DATABASE_BACKUP_RELATIVE / filename ))
1795- self ._log_database_backup (date , name )
1796- util .SMlog ("[database_backup] Created: {}" .format (filename ), priority = util .LOG_INFO )
1795+ self ._linstor .controller_backupdb (f"../linstor.d/db-backups/{ filename } " )
1796+ util .SMlog (f"[database_backup] Created: { filename } " , priority = util .LOG_INFO )
17971797
17981798 def database_invalidation (self ):
1799- for directory in (Path (DATABASE_PATH ), DATABASE_BACKUP_LOGDIR ):
1800- file_ok = 0
1799+ """
1800+ Removes old backup based on two criterias:
1801+ - Validity of the zipfile done by self._check_database_backup.
1802+ - Number of valid files found, only the nth latest are kept.
1803+ """
1804+ for directory in (DATABASE_BACKUP_DIR_MAIN , DATABASE_BACKUP_DIR_SPARE ):
1805+ valid_backup_count = 0
18011806 # Validate file and apply retention
18021807 for database_backup_file , _ in self ._get_sorted_database_backup (directory ):
18031808 try :
18041809 self ._check_database_backup (database_backup_file )
1805- file_ok += 1
1806- if file_ok < DATABASE_BACKUP_RETENTION :
1810+ valid_backup_count += 1
1811+ if valid_backup_count < DATABASE_BACKUP_RETENTION :
18071812 continue
18081813 except LinstorDatabaseBackupError as error :
1809- util .SMlog ("[database_backup] Check failed: `{}` [{}]" .format (
1810- error , database_backup_file ), priority = util .LOG_ERR )
1814+ util .SMlog (f"[database_backup] Check failed: `{ error } ` [{ database_backup_file } ]" , priority = util .LOG_ERR )
18111815 with contextlib .suppress (OSError ):
18121816 os .unlink (database_backup_file )
18131817
@@ -2667,24 +2671,6 @@ def _get_volume_properties(self, volume_uuid):
26672671 properties .namespace = self ._build_volume_namespace (volume_uuid )
26682672 return properties
26692673
2670- def _log_database_backup (self , date , name ):
2671- """Log a database backup operation: "date name"
2672- We cannot assume the Pool Master is the same as the Linstor Controller,
2673- this file is on the Pool Master, and serves for the throttling."""
2674- os .makedirs (DATABASE_BACKUP_LOGDIR , mode = 0o755 , exist_ok = True )
2675- with open (DATABASE_BACKUP_LOGFILE , "a" , encoding = "utf8" ) as f :
2676- f .write (f"{ date } { name [:15 ]} \n " )
2677-
2678- def _get_latest_logged_database_backup_date (self ):
2679- # get last log line if it exists, and return the corresponding date
2680- try :
2681- with open (DATABASE_BACKUP_LOGFILE , "rb" ) as f :
2682- # seek from the end, a line length can't be more than 32
2683- f .seek (- min (os .stat (DATABASE_BACKUP_LOGFILE ).st_size , 32 ), os .SEEK_END )
2684- return f .read ().decode ().splitlines ()[- 1 ].split ()[0 ]
2685- except FileNotFoundError :
2686- return "20000101_000000"
2687-
26882674 def _list_database_backup (self , database_backup_dir ):
26892675 for path in database_backup_dir .glob (DATABASE_BACKUP_NAME_FORMAT .format (
26902676 "20[0-9][0-9][01][0-9][0-3][0-9]_[0-2][0-9][0-5][0-9][0-5][0-9]" , "*" ) + ".zip" ):
@@ -2698,6 +2684,11 @@ def _get_sorted_database_backup(self, database_backup_dir):
26982684 reverse = True ,
26992685 key = lambda p : p [1 ])
27002686
2687+ def _get_latest_database_backup (self , name = "*" ):
2688+ return max (self ._list_database_backup (DATABASE_BACKUP_DIR_MAIN , name ),
2689+ default = (None , datetime .fromtimestamp (0 )),
2690+ key = lambda p : p [1 ])
2691+
27012692 def _check_database_backup (self , database_backup_file ):
27022693 try :
27032694 with zipfile .ZipFile (database_backup_file , mode = "r" ) as archive :
@@ -2711,7 +2702,7 @@ def _check_database_backup(self, database_backup_file):
27112702 raise LinstorDatabaseBackupError ("linstordb.mv.db is empty" )
27122703 except LinstorDatabaseBackupError :
27132704 raise
2714- except Exception as e :
2705+ except ( FileNotFoundError , zipfile . BadZipFile ) as e :
27152706 raise LinstorDatabaseBackupError (e ) from e
27162707
27172708 @classmethod
0 commit comments