Add "break-lock" action for removing any repository and cache locks leftover from Borg aborting (#357).
This commit is contained in:
parent
2774c2e4c0
commit
ba8fbe7a44
8 changed files with 157 additions and 1 deletions
2
NEWS
2
NEWS
|
@ -1,4 +1,6 @@
|
||||||
1.7.3.dev0
|
1.7.3.dev0
|
||||||
|
* #357: Add "break-lock" action for removing any repository and cache locks leftover from Borg
|
||||||
|
aborting.
|
||||||
* #587: When the "read_special" option is true or database hooks are enabled, auto-exclude special
|
* #587: When the "read_special" option is true or database hooks are enabled, auto-exclude special
|
||||||
files for a "create" action to prevent Borg from hanging.
|
files for a "create" action to prevent Borg from hanging.
|
||||||
* #587: Warn when ignoring a configured "read_special" value of false, as true is needed when
|
* #587: Warn when ignoring a configured "read_special" value of false, as true is needed when
|
||||||
|
|
31
borgmatic/borg/break_lock.py
Normal file
31
borgmatic/borg/break_lock.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from borgmatic.borg import environment, flags
|
||||||
|
from borgmatic.execute import execute_command
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def break_lock(
|
||||||
|
repository, storage_config, local_borg_version, local_path='borg', remote_path=None,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Given a local or remote repository path, a storage configuration dict, the local Borg version,
|
||||||
|
and optional local and remote Borg paths, break any repository and cache locks leftover from Borg
|
||||||
|
aborting.
|
||||||
|
'''
|
||||||
|
umask = storage_config.get('umask', None)
|
||||||
|
lock_wait = storage_config.get('lock_wait', None)
|
||||||
|
|
||||||
|
full_command = (
|
||||||
|
(local_path, 'break-lock')
|
||||||
|
+ (('--remote-path', remote_path) if remote_path else ())
|
||||||
|
+ (('--umask', str(umask)) if umask else ())
|
||||||
|
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
|
||||||
|
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||||
|
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||||
|
+ flags.make_repository_flags(repository, local_borg_version)
|
||||||
|
)
|
||||||
|
|
||||||
|
borg_environment = environment.make_environment(storage_config)
|
||||||
|
execute_command(full_command, borg_local_path=local_path, extra_environment=borg_environment)
|
|
@ -407,6 +407,7 @@ def create_archive(
|
||||||
|
|
||||||
# If read_special is enabled, exclude files that might cause Borg to hang.
|
# If read_special is enabled, exclude files that might cause Borg to hang.
|
||||||
if read_special:
|
if read_special:
|
||||||
|
logger.debug(f'{repository}: Collecting special file paths')
|
||||||
special_file_paths = collect_special_file_paths(
|
special_file_paths = collect_special_file_paths(
|
||||||
create_command,
|
create_command,
|
||||||
local_path,
|
local_path,
|
||||||
|
|
|
@ -19,6 +19,7 @@ SUBPARSER_ALIASES = {
|
||||||
'rinfo': [],
|
'rinfo': [],
|
||||||
'info': ['-i'],
|
'info': ['-i'],
|
||||||
'transfer': [],
|
'transfer': [],
|
||||||
|
'break-lock': [],
|
||||||
'borg': [],
|
'borg': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,6 +775,19 @@ def make_parsers():
|
||||||
)
|
)
|
||||||
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
info_group.add_argument('-h', '--help', action='help', help='Show this help message and exit')
|
||||||
|
|
||||||
|
break_lock_parser = subparsers.add_parser(
|
||||||
|
'break-lock',
|
||||||
|
aliases=SUBPARSER_ALIASES['break-lock'],
|
||||||
|
help='Break the repository and cache locks left behind by Borg aborting',
|
||||||
|
description='Break Borg repository and cache locks left behind by Borg aborting',
|
||||||
|
add_help=False,
|
||||||
|
)
|
||||||
|
break_lock_group = break_lock_parser.add_argument_group('break-lock arguments')
|
||||||
|
break_lock_group.add_argument(
|
||||||
|
'--repository',
|
||||||
|
help='Path of repository to break the lock for, defaults to the configured repository if there is only one',
|
||||||
|
)
|
||||||
|
|
||||||
borg_parser = subparsers.add_parser(
|
borg_parser = subparsers.add_parser(
|
||||||
'borg',
|
'borg',
|
||||||
aliases=SUBPARSER_ALIASES['borg'],
|
aliases=SUBPARSER_ALIASES['borg'],
|
||||||
|
|
|
@ -13,6 +13,7 @@ import pkg_resources
|
||||||
|
|
||||||
import borgmatic.commands.completion
|
import borgmatic.commands.completion
|
||||||
from borgmatic.borg import borg as borg_borg
|
from borgmatic.borg import borg as borg_borg
|
||||||
|
from borgmatic.borg import break_lock as borg_break_lock
|
||||||
from borgmatic.borg import check as borg_check
|
from borgmatic.borg import check as borg_check
|
||||||
from borgmatic.borg import compact as borg_compact
|
from borgmatic.borg import compact as borg_compact
|
||||||
from borgmatic.borg import create as borg_create
|
from borgmatic.borg import create as borg_create
|
||||||
|
@ -731,6 +732,18 @@ def run_actions(
|
||||||
)
|
)
|
||||||
if json_output: # pragma: nocover
|
if json_output: # pragma: nocover
|
||||||
yield json.loads(json_output)
|
yield json.loads(json_output)
|
||||||
|
if 'break-lock' in arguments:
|
||||||
|
if arguments['break-lock'].repository is None or validate.repositories_match(
|
||||||
|
repository, arguments['break-lock'].repository
|
||||||
|
):
|
||||||
|
logger.warning(f'{repository}: Breaking repository and cache locks')
|
||||||
|
borg_break_lock.break_lock(
|
||||||
|
repository,
|
||||||
|
storage,
|
||||||
|
local_borg_version,
|
||||||
|
local_path=local_path,
|
||||||
|
remote_path=remote_path,
|
||||||
|
)
|
||||||
if 'borg' in arguments:
|
if 'borg' in arguments:
|
||||||
if arguments['borg'].repository is None or validate.repositories_match(
|
if arguments['borg'].repository is None or validate.repositories_match(
|
||||||
repository, arguments['borg'].repository
|
repository, arguments['borg'].repository
|
||||||
|
|
|
@ -4,7 +4,7 @@ COPY . /app
|
||||||
RUN apk add --no-cache py3-pip py3-ruamel.yaml py3-ruamel.yaml.clib
|
RUN apk add --no-cache py3-pip py3-ruamel.yaml py3-ruamel.yaml.clib
|
||||||
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
|
RUN pip install --no-cache /app && generate-borgmatic-config && chmod +r /etc/borgmatic/config.yaml
|
||||||
RUN borgmatic --help > /command-line.txt \
|
RUN borgmatic --help > /command-line.txt \
|
||||||
&& for action in rcreate transfer prune compact create check extract export-tar mount umount restore rlist list rinfo info borg; do \
|
&& for action in rcreate transfer prune compact create check extract export-tar mount umount restore rlist list rinfo info break-lock borg; do \
|
||||||
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
|
echo -e "\n--------------------------------------------------------------------------------\n" >> /command-line.txt \
|
||||||
&& borgmatic "$action" --help >> /command-line.txt; done
|
&& borgmatic "$action" --help >> /command-line.txt; done
|
||||||
|
|
||||||
|
|
70
tests/unit/borg/test_break_lock.py
Normal file
70
tests/unit/borg/test_break_lock.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from flexmock import flexmock
|
||||||
|
|
||||||
|
from borgmatic.borg import break_lock as module
|
||||||
|
|
||||||
|
from ..test_verbosity import insert_logging_mock
|
||||||
|
|
||||||
|
|
||||||
|
def insert_execute_command_mock(command):
|
||||||
|
flexmock(module.environment).should_receive('make_environment')
|
||||||
|
flexmock(module).should_receive('execute_command').with_args(
|
||||||
|
command, borg_local_path='borg', extra_environment=None,
|
||||||
|
).once()
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_lock_calls_borg_with_required_flags():
|
||||||
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
|
insert_execute_command_mock(('borg', 'break-lock', 'repo'))
|
||||||
|
|
||||||
|
module.break_lock(
|
||||||
|
repository='repo', storage_config={}, local_borg_version='1.2.3',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_lock_calls_borg_with_remote_path_flags():
|
||||||
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
|
insert_execute_command_mock(('borg', 'break-lock', '--remote-path', 'borg1', 'repo'))
|
||||||
|
|
||||||
|
module.break_lock(
|
||||||
|
repository='repo', storage_config={}, local_borg_version='1.2.3', remote_path='borg1',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_lock_calls_borg_with_umask_flags():
|
||||||
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
|
insert_execute_command_mock(('borg', 'break-lock', '--umask', '0770', 'repo'))
|
||||||
|
|
||||||
|
module.break_lock(
|
||||||
|
repository='repo', storage_config={'umask': '0770'}, local_borg_version='1.2.3',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_lock_calls_borg_with_lock_wait_flags():
|
||||||
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
|
insert_execute_command_mock(('borg', 'break-lock', '--lock-wait', '5', 'repo'))
|
||||||
|
|
||||||
|
module.break_lock(
|
||||||
|
repository='repo', storage_config={'lock_wait': '5'}, local_borg_version='1.2.3',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_lock_with_log_info_calls_borg_with_info_parameter():
|
||||||
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
|
insert_execute_command_mock(('borg', 'break-lock', '--info', 'repo'))
|
||||||
|
insert_logging_mock(logging.INFO)
|
||||||
|
|
||||||
|
module.break_lock(
|
||||||
|
repository='repo', storage_config={}, local_borg_version='1.2.3',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_break_lock_with_log_debug_calls_borg_with_debug_flags():
|
||||||
|
flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
|
||||||
|
insert_execute_command_mock(('borg', 'break-lock', '--debug', '--show-rc', 'repo'))
|
||||||
|
insert_logging_mock(logging.DEBUG)
|
||||||
|
|
||||||
|
module.break_lock(
|
||||||
|
repository='repo', storage_config={}, local_borg_version='1.2.3',
|
||||||
|
)
|
|
@ -712,6 +712,31 @@ def test_run_actions_does_not_raise_for_info_action():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_actions_does_not_raise_for_break_lock_action():
|
||||||
|
flexmock(module.validate).should_receive('repositories_match').and_return(True)
|
||||||
|
flexmock(module.borg_break_lock).should_receive('break_lock')
|
||||||
|
arguments = {
|
||||||
|
'global': flexmock(monitoring_verbosity=1, dry_run=False),
|
||||||
|
'break-lock': flexmock(repository=flexmock()),
|
||||||
|
}
|
||||||
|
|
||||||
|
list(
|
||||||
|
module.run_actions(
|
||||||
|
arguments=arguments,
|
||||||
|
config_filename='test.yaml',
|
||||||
|
location={'repositories': ['repo']},
|
||||||
|
storage={},
|
||||||
|
retention={},
|
||||||
|
consistency={},
|
||||||
|
hooks={},
|
||||||
|
local_path=None,
|
||||||
|
remote_path=None,
|
||||||
|
local_borg_version=None,
|
||||||
|
repository_path='repo',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_run_actions_does_not_raise_for_borg_action():
|
def test_run_actions_does_not_raise_for_borg_action():
|
||||||
flexmock(module.validate).should_receive('repositories_match').and_return(True)
|
flexmock(module.validate).should_receive('repositories_match').and_return(True)
|
||||||
flexmock(module.borg_rlist).should_receive('resolve_archive_name').and_return(flexmock())
|
flexmock(module.borg_rlist).should_receive('resolve_archive_name').and_return(flexmock())
|
||||||
|
|
Loading…
Reference in a new issue