snapshot: Fix snapshots filling up the disk

- Snapper's cleanup algorithms don't apply limits unless they are specified in
the form of ranges. Changed all limit values in the configuration to ranges.
- Also, all MIN_AGE values have been set to 0 to avoid the disk space filling up
in case of a large number of snapshots generated in a very short amount of time.
- FREE_LIMIT has been increased to 30% from the default 20% since backup
archives also take up disk space on the root partition.

Fixes #1435

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
Joseph Nuthalapati 2019-01-06 02:50:44 +05:30 committed by Sunil Mohan Adapa
parent 8dbf73d3f5
commit 8ba0bda869
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
3 changed files with 86 additions and 29 deletions

View File

@ -39,6 +39,9 @@ def parse_arguments():
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('setup', help='Configure snapper')
subparsers.add_parser(
'migrate',
help='Migrate existing user configuration to the new format')
subparsers.add_parser('list', help='List snapshots')
subparsers.add_parser('create', help='Create snapshot')
subparsers.add_parser('get-config', help='Configurations of snapshot')
@ -85,11 +88,20 @@ def subcommand_setup(_):
def _set_default_config():
command = [
'snapper', 'set-config', 'TIMELINE_CREATE=yes',
'TIMELINE_LIMIT_HOURLY=10', 'TIMELINE_LIMIT_MONTHLY=2',
'TIMELINE_LIMIT_WEEKLY=2', 'TIMELINE_LIMIT_YEARLY=0',
'TIMELINE_LIMIT_DAILY=3', 'NUMBER_MIN_AGE=1296000', 'NUMBER_LIMIT=0',
'NUMBER_LIMIT_IMPORTANT=4-10'
'snapper',
'set-config',
'TIMELINE_CREATE=yes',
'TIMELINE_MIN_AGE=0',
'TIMELINE_LIMIT_HOURLY=0-10',
'TIMELINE_LIMIT_DAILY=0-3',
'TIMELINE_LIMIT_WEEKLY=0-2',
'TIMELINE_LIMIT_MONTHLY=0-2',
'TIMELINE_LIMIT_YEARLY=0-0',
'NUMBER_MIN_AGE=0',
'NUMBER_LIMIT=0-100',
'NUMBER_LIMIT_IMPORTANT=0-20',
'EMPTY_PRE_POST_MIN_AGE=0',
'FREE_LIMIT=0.3',
]
subprocess.run(command, check=True)
@ -223,7 +235,7 @@ def subcommand_set_config(arguments):
subprocess.run(command, check=True)
def subcommand_get_config(_):
def _get_config():
command = ['snapper', 'get-config']
process = subprocess.run(command, stdout=subprocess.PIPE, check=True)
lines = process.stdout.decode().splitlines()
@ -231,9 +243,45 @@ def subcommand_get_config(_):
for line in lines[2:]:
parts = [part.strip() for part in line.split('|')]
config[parts[0]] = parts[1]
return config
def subcommand_get_config(_):
config = _get_config()
print(json.dumps(config))
def subcommand_migrate(_):
"""Migrate existing user configuration for snapshots
to the new configuration format.
Add in version 4 of the snapshots module.
This command will not check or perform first setup steps.
"""
config = _get_config()
def convert_to_range(key):
value = config[key]
value = value if '-' in value else '0-{}'.format(value)
return '{}={}'.format(key, value)
command = [
'snapper',
'set-config',
'TIMELINE_MIN_AGE=0',
convert_to_range('TIMELINE_LIMIT_HOURLY'),
convert_to_range('TIMELINE_LIMIT_DAILY'),
convert_to_range('TIMELINE_LIMIT_WEEKLY'),
convert_to_range('TIMELINE_LIMIT_MONTHLY'),
convert_to_range('TIMELINE_LIMIT_YEARLY'),
'NUMBER_MIN_AGE=0',
'NUMBER_LIMIT=0-100',
'NUMBER_LIMIT_IMPORTANT=0-20',
'EMPTY_PRE_POST_MIN_AGE=0',
'FREE_LIMIT=0.3',
]
subprocess.run(command, check=True)
def subcommand_kill_daemon(_):
"""Kill the snapper daemon.

View File

@ -28,7 +28,7 @@ from plinth.menu import main_menu
from .manifest import backup
version = 3
version = 4
managed_packages = ['snapper']
@ -67,13 +67,16 @@ def init():
def setup(helper, old_version=None):
"""Install and configure the module."""
helper.install(managed_packages)
helper.call('post', actions.superuser_run, 'snapshot', ['setup'])
if old_version:
helper.call('post', actions.superuser_run, 'snapshot', ['migrate'])
else:
helper.call('post', actions.superuser_run, 'snapshot', ['setup'])
def load_augeas():
"""Initialize Augeas."""
aug = augeas.Augeas(
flags=augeas.Augeas.NO_LOAD + augeas.Augeas.NO_MODL_AUTOLOAD)
aug = augeas.Augeas(flags=augeas.Augeas.NO_LOAD +
augeas.Augeas.NO_MODL_AUTOLOAD)
# shell-script config file lens
aug.set('/augeas/load/Shellvars/lens', 'Shellvars.lns')
@ -96,21 +99,24 @@ def get_configuration():
def get_boolean_choice(status):
return ('yes', 'Enabled') if status else ('no', 'Disabled')
def get_max_from_range(key):
return output[key].split('-')[-1]
return {
'enable_timeline_snapshots':
get_boolean_choice(output['TIMELINE_CREATE'] == 'yes'),
'enable_software_snapshots':
get_boolean_choice(is_apt_snapshots_enabled(aug)),
'hourly_limit':
output['TIMELINE_LIMIT_HOURLY'],
get_max_from_range('TIMELINE_LIMIT_HOURLY'),
'daily_limit':
output['TIMELINE_LIMIT_DAILY'],
get_max_from_range('TIMELINE_LIMIT_DAILY'),
'weekly_limit':
output['TIMELINE_LIMIT_WEEKLY'],
'yearly_limit':
output['TIMELINE_LIMIT_YEARLY'],
get_max_from_range('TIMELINE_LIMIT_WEEKLY'),
'monthly_limit':
output['TIMELINE_LIMIT_MONTHLY'],
get_max_from_range('TIMELINE_LIMIT_MONTHLY'),
'yearly_limit':
get_max_from_range('TIMELINE_LIMIT_YEARLY'),
'number_min_age':
round(int(output['NUMBER_MIN_AGE']) / 86400),
}

View File

@ -98,25 +98,28 @@ def update_configuration(request, old_status, new_status):
def make_config(args):
key, stamp = args[0], args[1]
if old_status[key] != new_status[key]:
return stamp.format(new_status[key])
if 'limit' in key:
return stamp.format('0-{}'.format(new_status[key]))
else:
return stamp.format(new_status[key])
else:
return None
new_status['number_min_age'] = int(new_status['number_min_age']) * 86400
config = filter(None,
map(make_config, [
('enable_timeline_snapshots', 'TIMELINE_CREATE={}'),
('hourly_limit', 'TIMELINE_LIMIT_HOURLY={}'),
('daily_limit', 'TIMELINE_LIMIT_DAILY={}'),
('weekly_limit', 'TIMELINE_LIMIT_WEEKLY={}'),
('monthly_limit', 'TIMELINE_LIMIT_MONTHLY={}'),
('yearly_limit', 'TIMELINE_LIMIT_YEARLY={}'),
('number_min_age', 'NUMBER_MIN_AGE={}'),
]))
config = filter(
None,
map(make_config, [
('enable_timeline_snapshots', 'TIMELINE_CREATE={}'),
('hourly_limit', 'TIMELINE_LIMIT_HOURLY={}'),
('daily_limit', 'TIMELINE_LIMIT_DAILY={}'),
('weekly_limit', 'TIMELINE_LIMIT_WEEKLY={}'),
('monthly_limit', 'TIMELINE_LIMIT_MONTHLY={}'),
('yearly_limit', 'TIMELINE_LIMIT_YEARLY={}'),
('number_min_age', 'NUMBER_MIN_AGE={}'),
]))
if old_status['enable_software_snapshots'] != new_status[
'enable_software_snapshots']:
if old_status['enable_software_snapshots'] != new_status['enable_software_snapshots']:
if new_status['enable_software_snapshots'] == 'yes':
actions.superuser_run('snapshot', ['disable-apt-snapshot', 'no'])
else: