Implement optional random delay for backups

This commit is contained in:
deajan 2025-06-12 14:36:01 +02:00
parent 7179342ef5
commit ceed283969
11 changed files with 61 additions and 4 deletions

View file

@ -52,6 +52,7 @@ groups:
repo_password:
repo_password_command:
minimum_backup_age: 1435
random_delay_before_backup: 200
upload_speed: 800 Mib
download_speed: 0 Mib
backend_connections: 0

View file

@ -52,6 +52,7 @@ groups:
repo_password:
repo_password_command:
minimum_backup_age: 1435
random_delay_before_backup: 200
upload_speed: 800 Mib
download_speed: 0 Mib
backend_connections: 0

View file

@ -51,6 +51,7 @@ groups:
repo_password:
repo_password_command:
minimum_backup_age: 1435
random_delay_before_backup: 200
upload_speed: 800 Mib
download_speed: 0 Mib
backend_connections: 0

View file

@ -178,6 +178,7 @@ empty_config_dict = {
# Minimum time between two backups, in minutes
# Set to zero in order to disable time checks
"minimum_backup_age": 1435,
"random_delay_before_backup": 200, # Random delay in minutes added to a backup launch
"upload_speed": "800 Mib", # allows BytesConverter units, use 0 for unlimited upload speed
"download_speed": "0 Mib", # allows BytesConverter units, use 0 for unlimited download speed
"backend_connections": 0, # Fine tune simultaneous connections to backend, use 0 for standard configuration

View file

@ -3,7 +3,7 @@
#
# This file is part of npbackup
__intname__ = "npbackup.gui.core.runner"
__intname__ = "npbackup.core.runner"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2022-2025 NetInvent"
__license__ = "GPL-3.0-only"
@ -15,6 +15,7 @@ import os
import logging
import tempfile
import queue
import time
from datetime import datetime, timedelta, timezone
from functools import wraps
from copy import deepcopy
@ -286,6 +287,7 @@ class NPBackupRunner:
self._no_lock = False
self.restic_runner = None
self.minimum_backup_age = None
self.random_delay_before_backup = None
self._exec_time = None
# Error /warning messages to add for json output
@ -718,6 +720,8 @@ class NPBackupRunner:
else:
# pylint: disable=E1101 (no-member)
operation = fn.__name__
# Actual operation concurrency check
if __check_concurrency and operation in locking_operations:
pid_file = os.path.join(
tempfile.gettempdir(), "{}.pid".format(__intname__)
@ -1040,6 +1044,13 @@ class NPBackupRunner:
except (KeyError, ValueError, TypeError):
# In doubt, launch the backup regardless of last backup age
self.minimum_backup_age = 0
try:
self.random_delay_before_backup = int(
self.repo_config.g("repo_opts.random_delay_before_backup")
)
except (KeyError, ValueError, TypeError):
# In doubt, launch the backup regardless of last backup age
self.random_delay_before_backup = 0
self.restic_runner.verbose = self.verbose
self.restic_runner.dry_run = self.dry_run
@ -1308,15 +1319,14 @@ class NPBackupRunner:
def backup(
self,
force: bool = False,
honor_delay: bool = True,
read_from_stdin: bool = False,
stdin_filename: str = None,
) -> bool:
"""
Run backup after checking if no recent backup exists, unless force == True
"""
start_time = datetime.now(timezone.utc)
repo_name = self.repo_config.g("name")
stdin_from_command = self.repo_config.g("backup_opts.stdin_from_command")
if not stdin_filename:
stdin_filename = self.repo_config.g("backup_opts.stdin_filename")
@ -1445,6 +1455,30 @@ class NPBackupRunner:
return self.convert_to_json_output(True, msg)
self.restic_runner.verbose = self.verbose
# Now add a random delay before backup if configured
# We also need to make sure that npbackup executions that happen between this delay don't actually run
if self.random_delay_before_backup and honor_delay:
# Random delay before backup
delay_backup_seconds = randint(0, self.random_delay_before_backup * 60)
delay_file = os.path.join(
tempfile.gettempdir(), "{}.delay".format(__intname__)
)
try:
with npbackup.pidfile_ng.PIDFile(
delay_file, check_full_commandline=False, identifier=repo_name
):
self.write_logs(
f"Delaying backup for {delay_backup_seconds} seconds",
level="info",
)
time.sleep(delay_backup_seconds)
except npbackup.pidfile_ng.AlreadyRunningError:
self.write_logs(
f"There is already a delayed NPBackup operation (full_concurrency = {self.full_concurrency}, repo_aware_concurrency = {self.repo_aware_concurrency}). Will not launch backup to avoid concurrency",
level="info",
)
return True
# Run backup preps here
if source_type in (
None,

View file

@ -528,6 +528,7 @@ def backup(repo_config: dict) -> bool:
__no_lock=__no_lock,
__full_concurrency=__full_concurrency,
__repo_aware_concurrency=__repo_aware_concurrency,
honor_delay=False,
)
try:
return result["result"]

View file

@ -1608,6 +1608,20 @@ def config_gui(full_config: dict, config_file: str):
sg.Input(key="repo_opts.minimum_backup_age", size=(8, 1)),
sg.Text(_t("generic.minutes")),
],
[
sg.Text(
_t("config_gui.random_delay_before_backup"),
size=(40, 2),
),
sg.Image(
NON_INHERITED_ICON,
key="inherited.repo_opts.random_delay_before_backup",
tooltip=_t("config_gui.group_inherited"),
pad=1,
),
sg.Input(key="repo_opts.random_delay_before_backup", size=(8, 1)),
sg.Text(_t("generic.minutes")),
],
[
sg.Text(_t("config_gui.upload_speed"), size=(40, 1)),
sg.Image(

View file

@ -32,6 +32,7 @@ en:
additional_restore_only_parameters: Additional restore only parameters
minimum_backup_age: Minimum delay between two backups
random_delay_before_backup: Random delay before launching backup
backup_repo_uri: backup repo URI / path
backup_repo_password: Backup repo encryption password
backup_repo_password_command: Command that returns backup repo encryption password

View file

@ -33,6 +33,7 @@ fr:
additional_restore_only_parameters: Paramètres supplémentaires de restauration
minimum_backup_age: Délai minimal entre deux sauvegardes
random_delay_before_backup: Délai aléatoire avant la sauvegarde
backup_repo_uri: URI / chemin local dépot de sauvegarde
backup_repo_password: Mot de passe (chiffrement) dépot de sauvegarde
backup_repo_password_command: Commande qui retourne le mot de passe de chiffrement dépot

View file

@ -54,6 +54,7 @@ groups:
repo_password:
repo_password_command:
minimum_backup_age: 1435
random_delay_before_backup: 200
upload_speed: 100 Mib
download_speed: 0 Mib
backend_connections: 0

View file

@ -55,6 +55,7 @@ groups:
repo_password:
repo_password_command:
minimum_backup_age: 1435
random_delay_before_backup: 200
upload_speed: 100 Mib
download_speed: 0 Mib
backend_connections: 0