Add an "only_run_on" option to consistency checks so you can limit a check to running on particular days of the week (#785).
This commit is contained in:
parent
ebde88ccaa
commit
593c956d33
5 changed files with 196 additions and 3 deletions
3
NEWS
3
NEWS
|
@ -1,4 +1,7 @@
|
||||||
1.8.13.dev0
|
1.8.13.dev0
|
||||||
|
* #785: Add an "only_run_on" option to consistency checks so you can limit a check to running on
|
||||||
|
particular days of the week. See the documentation for more information:
|
||||||
|
https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#check-days
|
||||||
* #886: Fix a PagerDuty hook traceback with Python < 3.10.
|
* #886: Fix a PagerDuty hook traceback with Python < 3.10.
|
||||||
* #889: Fix the Healthchecks ping body size limit, restoring it to the documented 100,000 bytes.
|
* #889: Fix the Healthchecks ping body size limit, restoring it to the documented 100,000 bytes.
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import calendar
|
||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import itertools
|
import itertools
|
||||||
|
@ -99,12 +100,17 @@ def parse_frequency(frequency):
|
||||||
raise ValueError(f"Could not parse consistency check frequency '{frequency}'")
|
raise ValueError(f"Could not parse consistency check frequency '{frequency}'")
|
||||||
|
|
||||||
|
|
||||||
|
WEEKDAY_DAYS = calendar.day_name[0:5]
|
||||||
|
WEEKEND_DAYS = calendar.day_name[5:7]
|
||||||
|
|
||||||
|
|
||||||
def filter_checks_on_frequency(
|
def filter_checks_on_frequency(
|
||||||
config,
|
config,
|
||||||
borg_repository_id,
|
borg_repository_id,
|
||||||
checks,
|
checks,
|
||||||
force,
|
force,
|
||||||
archives_check_id=None,
|
archives_check_id=None,
|
||||||
|
datetime_now=datetime.datetime.now,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Given a configuration dict with a "checks" sequence of dicts, a Borg repository ID, a sequence
|
Given a configuration dict with a "checks" sequence of dicts, a Borg repository ID, a sequence
|
||||||
|
@ -143,6 +149,29 @@ def filter_checks_on_frequency(
|
||||||
if checks and check not in checks:
|
if checks and check not in checks:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
only_run_on = check_config.get('only_run_on')
|
||||||
|
if only_run_on:
|
||||||
|
# Use a dict instead of a set to preserve ordering.
|
||||||
|
days = dict.fromkeys(only_run_on)
|
||||||
|
|
||||||
|
if 'weekday' in days:
|
||||||
|
days = {
|
||||||
|
**dict.fromkeys(day for day in days if day != 'weekday'),
|
||||||
|
**dict.fromkeys(WEEKDAY_DAYS),
|
||||||
|
}
|
||||||
|
if 'weekend' in days:
|
||||||
|
days = {
|
||||||
|
**dict.fromkeys(day for day in days if day != 'weekend'),
|
||||||
|
**dict.fromkeys(WEEKEND_DAYS),
|
||||||
|
}
|
||||||
|
|
||||||
|
if calendar.day_name[datetime_now().weekday()] not in days:
|
||||||
|
logger.info(
|
||||||
|
f"Skipping {check} check due to day of the week; check only runs on {'/'.join(days)} (use --force to check anyway)"
|
||||||
|
)
|
||||||
|
filtered_checks.remove(check)
|
||||||
|
continue
|
||||||
|
|
||||||
frequency_delta = parse_frequency(check_config.get('frequency'))
|
frequency_delta = parse_frequency(check_config.get('frequency'))
|
||||||
if not frequency_delta:
|
if not frequency_delta:
|
||||||
continue
|
continue
|
||||||
|
@ -153,8 +182,8 @@ def filter_checks_on_frequency(
|
||||||
|
|
||||||
# If we've not yet reached the time when the frequency dictates we're ready for another
|
# If we've not yet reached the time when the frequency dictates we're ready for another
|
||||||
# check, skip this check.
|
# check, skip this check.
|
||||||
if datetime.datetime.now() < check_time + frequency_delta:
|
if datetime_now() < check_time + frequency_delta:
|
||||||
remaining = check_time + frequency_delta - datetime.datetime.now()
|
remaining = check_time + frequency_delta - datetime_now()
|
||||||
logger.info(
|
logger.info(
|
||||||
f'Skipping {check} check due to configured frequency; {remaining} until next check (use --force to check anyway)'
|
f'Skipping {check} check due to configured frequency; {remaining} until next check (use --force to check anyway)'
|
||||||
)
|
)
|
||||||
|
|
|
@ -546,6 +546,20 @@ properties:
|
||||||
"always": running this check every time checks
|
"always": running this check every time checks
|
||||||
are run.
|
are run.
|
||||||
example: 2 weeks
|
example: 2 weeks
|
||||||
|
only_run_on:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
After the "frequency" duration has elapsed, only
|
||||||
|
run this check if the current day of the week
|
||||||
|
matches one of these values (the name of a day of
|
||||||
|
the week in the current locale). "weekday" and
|
||||||
|
"weekend" are also accepted. Defaults to running
|
||||||
|
the check on any day of the week.
|
||||||
|
example:
|
||||||
|
- Saturday
|
||||||
|
- Sunday
|
||||||
- required: [name]
|
- required: [name]
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
properties:
|
properties:
|
||||||
|
@ -579,6 +593,20 @@ properties:
|
||||||
"always": running this check every time checks
|
"always": running this check every time checks
|
||||||
are run.
|
are run.
|
||||||
example: 2 weeks
|
example: 2 weeks
|
||||||
|
only_run_on:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
After the "frequency" duration has elapsed, only
|
||||||
|
run this check if the current day of the week
|
||||||
|
matches one of these values (the name of a day of
|
||||||
|
the week in the current locale). "weekday" and
|
||||||
|
"weekend" are also accepted. Defaults to running
|
||||||
|
the check on any day of the week.
|
||||||
|
example:
|
||||||
|
- Saturday
|
||||||
|
- Sunday
|
||||||
max_duration:
|
max_duration:
|
||||||
type: integer
|
type: integer
|
||||||
description: |
|
description: |
|
||||||
|
@ -627,6 +655,20 @@ properties:
|
||||||
"always": running this check every time checks
|
"always": running this check every time checks
|
||||||
are run.
|
are run.
|
||||||
example: 2 weeks
|
example: 2 weeks
|
||||||
|
only_run_on:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
After the "frequency" duration has elapsed, only
|
||||||
|
run this check if the current day of the week
|
||||||
|
matches one of these values (the name of a day of
|
||||||
|
the week in the current locale). "weekday" and
|
||||||
|
"weekend" are also accepted. Defaults to running
|
||||||
|
the check on any day of the week.
|
||||||
|
example:
|
||||||
|
- Saturday
|
||||||
|
- Sunday
|
||||||
count_tolerance_percentage:
|
count_tolerance_percentage:
|
||||||
type: number
|
type: number
|
||||||
description: |
|
description: |
|
||||||
|
|
|
@ -242,6 +242,57 @@ check --force` runs `check` even if it's specified in the `skip_actions`
|
||||||
option.
|
option.
|
||||||
|
|
||||||
|
|
||||||
|
### Check days
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">New in version 1.8.13</span> You can
|
||||||
|
optionally configure checks to only run on particular days of the week. For
|
||||||
|
instance:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
checks:
|
||||||
|
- name: repository
|
||||||
|
only_run_on:
|
||||||
|
- Saturday
|
||||||
|
- Sunday
|
||||||
|
- name: archives
|
||||||
|
only_run_on:
|
||||||
|
- weekday
|
||||||
|
- name: spot
|
||||||
|
only_run_on:
|
||||||
|
- Friday
|
||||||
|
- weekend
|
||||||
|
```
|
||||||
|
|
||||||
|
Each day of the week is specified in the current locale (system
|
||||||
|
language/country settings). `weekend` and `weekday` are also accepted.
|
||||||
|
|
||||||
|
Just like with `frequency`, borgmatic only makes a best effort to run checks
|
||||||
|
on the given day of the week. For instance, if you run `borgmatic check`
|
||||||
|
daily, then every day borgmatic will have an opportunity to determine whether
|
||||||
|
your checks are configured to run on that day. If they are, then the checks
|
||||||
|
run. If not, they are skipped.
|
||||||
|
|
||||||
|
For instance, with the above configuration, if borgmatic is run on a Saturday,
|
||||||
|
the `repository` check will run. But on a Monday? The repository check will
|
||||||
|
get skipped. And if borgmatic is never run on a Saturday or a Sunday, that
|
||||||
|
check will never get a chance to run.
|
||||||
|
|
||||||
|
Also, the day of the week configuration applies *after* any configured
|
||||||
|
`frequency` for a check. So for instance, imagine the following configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
checks:
|
||||||
|
- name: repository
|
||||||
|
frequency: 2 weeks
|
||||||
|
only_run_on:
|
||||||
|
- Monday
|
||||||
|
```
|
||||||
|
|
||||||
|
If you run borgmatic daily with that configuration, then borgmatic will first
|
||||||
|
wait two weeks after the previous check before running the check again—on the
|
||||||
|
first Monday after the `frequency` duration elapses.
|
||||||
|
|
||||||
|
|
||||||
### Running only checks
|
### Running only checks
|
||||||
|
|
||||||
<span class="minilink minilink-addedin">New in version 1.7.1</span> If you
|
<span class="minilink minilink-addedin">New in version 1.7.1</span> If you
|
||||||
|
|
|
@ -113,6 +113,74 @@ def test_filter_checks_on_frequency_retains_check_without_frequency():
|
||||||
) == ('archives',)
|
) == ('archives',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_checks_on_frequency_retains_check_with_empty_only_run_on():
|
||||||
|
flexmock(module).should_receive('parse_frequency').and_return(None)
|
||||||
|
|
||||||
|
assert module.filter_checks_on_frequency(
|
||||||
|
config={'checks': [{'name': 'archives', 'only_run_on': []}]},
|
||||||
|
borg_repository_id='repo',
|
||||||
|
checks=('archives',),
|
||||||
|
force=False,
|
||||||
|
archives_check_id='1234',
|
||||||
|
datetime_now=flexmock(weekday=lambda: 0),
|
||||||
|
) == ('archives',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_checks_on_frequency_retains_check_with_only_run_on_matching_today():
|
||||||
|
flexmock(module).should_receive('parse_frequency').and_return(None)
|
||||||
|
|
||||||
|
assert module.filter_checks_on_frequency(
|
||||||
|
config={'checks': [{'name': 'archives', 'only_run_on': [module.calendar.day_name[0]]}]},
|
||||||
|
borg_repository_id='repo',
|
||||||
|
checks=('archives',),
|
||||||
|
force=False,
|
||||||
|
archives_check_id='1234',
|
||||||
|
datetime_now=flexmock(weekday=lambda: 0),
|
||||||
|
) == ('archives',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_checks_on_frequency_retains_check_with_only_run_on_matching_today_via_weekday_value():
|
||||||
|
flexmock(module).should_receive('parse_frequency').and_return(None)
|
||||||
|
|
||||||
|
assert module.filter_checks_on_frequency(
|
||||||
|
config={'checks': [{'name': 'archives', 'only_run_on': ['weekday']}]},
|
||||||
|
borg_repository_id='repo',
|
||||||
|
checks=('archives',),
|
||||||
|
force=False,
|
||||||
|
archives_check_id='1234',
|
||||||
|
datetime_now=flexmock(weekday=lambda: 0),
|
||||||
|
) == ('archives',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_checks_on_frequency_retains_check_with_only_run_on_matching_today_via_weekend_value():
|
||||||
|
flexmock(module).should_receive('parse_frequency').and_return(None)
|
||||||
|
|
||||||
|
assert module.filter_checks_on_frequency(
|
||||||
|
config={'checks': [{'name': 'archives', 'only_run_on': ['weekend']}]},
|
||||||
|
borg_repository_id='repo',
|
||||||
|
checks=('archives',),
|
||||||
|
force=False,
|
||||||
|
archives_check_id='1234',
|
||||||
|
datetime_now=flexmock(weekday=lambda: 6),
|
||||||
|
) == ('archives',)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_checks_on_frequency_skips_check_with_only_run_on_not_matching_today():
|
||||||
|
flexmock(module).should_receive('parse_frequency').and_return(None)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
module.filter_checks_on_frequency(
|
||||||
|
config={'checks': [{'name': 'archives', 'only_run_on': [module.calendar.day_name[5]]}]},
|
||||||
|
borg_repository_id='repo',
|
||||||
|
checks=('archives',),
|
||||||
|
force=False,
|
||||||
|
archives_check_id='1234',
|
||||||
|
datetime_now=flexmock(weekday=lambda: 0),
|
||||||
|
)
|
||||||
|
== ()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_checks_on_frequency_retains_check_with_elapsed_frequency():
|
def test_filter_checks_on_frequency_retains_check_with_elapsed_frequency():
|
||||||
flexmock(module).should_receive('parse_frequency').and_return(
|
flexmock(module).should_receive('parse_frequency').and_return(
|
||||||
module.datetime.timedelta(hours=1)
|
module.datetime.timedelta(hours=1)
|
||||||
|
@ -168,7 +236,7 @@ def test_filter_checks_on_frequency_skips_check_with_unelapsed_frequency():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_filter_checks_on_frequency_restains_check_with_unelapsed_frequency_and_force():
|
def test_filter_checks_on_frequency_retains_check_with_unelapsed_frequency_and_force():
|
||||||
assert module.filter_checks_on_frequency(
|
assert module.filter_checks_on_frequency(
|
||||||
config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
|
config={'checks': [{'name': 'archives', 'frequency': '1 hour'}]},
|
||||||
borg_repository_id='repo',
|
borg_repository_id='repo',
|
||||||
|
|
Loading…
Reference in a new issue