From 048fb83ee76fb9d8cb8b118f779b2d8acf0eb599 Mon Sep 17 00:00:00 2001 From: Franziska Kunsmann Date: Sat, 20 May 2023 07:46:23 +0200 Subject: [PATCH] bundles/apt: support spreading unattended-upgrades in a group --- bundles/apt/metadata.py | 7 +++- bundles/icinga2/files/icinga2/downtimes.conf | 29 ++++---------- bundles/icinga2/items.py | 40 +++++++++++++++++++- groups/features.py | 5 +++ hooks/test_unattended_upgrades_spread.py | 24 ++++++++++++ 5 files changed, 81 insertions(+), 24 deletions(-) create mode 100644 hooks/test_unattended_upgrades_spread.py diff --git a/bundles/apt/metadata.py b/bundles/apt/metadata.py index 141d89a..df84473 100644 --- a/bundles/apt/metadata.py +++ b/bundles/apt/metadata.py @@ -24,13 +24,18 @@ def patchday(metadata): day = metadata.get('apt/unattended-upgrades/day') hour = metadata.get('apt/unattended-upgrades/hour') + spread = metadata.get('apt/unattended-upgrades/spread_in_group', None) + if spread is not None: + spread_nodes = sorted(repo.nodes_in_group(spread)) + day += spread_nodes.index(node) + return { 'cron': { 'jobs': { 'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format( minute=node.magic_number % 30, hour=hour, - day=day, + day=day%7, ), }, }, diff --git a/bundles/icinga2/files/icinga2/downtimes.conf b/bundles/icinga2/files/icinga2/downtimes.conf index 0052816..6dffabd 100644 --- a/bundles/icinga2/files/icinga2/downtimes.conf +++ b/bundles/icinga2/files/icinga2/downtimes.conf @@ -1,31 +1,18 @@ -% for monitored_node in sorted(repo.nodes): -<% - auto_updates_enabled = ( - monitored_node.has_any_bundle(['apt', 'c3voc-addons']) - or ( - monitored_node.has_bundle('pacman') - and monitored_node.metadata.get('pacman/unattended-upgrades/is_enabled', False) - ) - ) and not monitored_node.metadata.get('icinga_options/exclude_from_monitoring', False) -%>\ -% if auto_updates_enabled: -object ScheduledDowntime "unattended_upgrades" { - host_name = "${monitored_node.name}" +% for dt in downtimes: +object ScheduledDowntime "${dt['name']}" { + host_name = "${dt['host']}" - author = "unattended-upgrades" - comment = "Downtime for upgrade-and-reboot of node ${monitored_node.name}" + author = "${dt['name']}" + comment = "${dt['comment']}" fixed = true ranges = { -% if monitored_node.has_bundle('pacman'): - "${days[monitored_node.metadata.get('pacman/unattended-upgrades/day')]}" = "${monitored_node.metadata.get('pacman/unattended-upgrades/hour')}:${monitored_node.magic_number%30}-${monitored_node.metadata.get('pacman/unattended-upgrades/hour')}:${(monitored_node.magic_number%30)+30}" -% else: - "${days[monitored_node.metadata.get('apt/unattended-upgrades/day')]}" = "${monitored_node.metadata.get('apt/unattended-upgrades/hour')}:${monitored_node.magic_number%30}-${monitored_node.metadata.get('apt/unattended-upgrades/hour')}:${(monitored_node.magic_number%30)+30}" -% endif +% for d,t in dt['times'].items(): + "${d}" = "${t}" +% endfor } child_options = "DowntimeTriggeredChildren" } -% endif % endfor diff --git a/bundles/icinga2/items.py b/bundles/icinga2/items.py index ff763a2..5f850b0 100644 --- a/bundles/icinga2/items.py +++ b/bundles/icinga2/items.py @@ -346,7 +346,8 @@ svc_systemd = { # The actual hosts and services management starts here bundles = set() -for rnode in repo.nodes: +downtimes = [] +for rnode in sorted(repo.nodes): if rnode.metadata.get('icinga_options/exclude_from_monitoring', False): continue @@ -388,6 +389,41 @@ for rnode in repo.nodes: bundles |= set(rnode.metadata.get('icinga2_api', {}).keys()) + if rnode.has_any_bundle(['apt', 'c3voc-addons']): + day = rnode.metadata.get('apt/unattended-upgrades/day') + hour = rnode.metadata.get('apt/unattended-upgrades/hour') + minute = rnode.magic_number%30 + + spread = rnode.metadata.get('apt/unattended-upgrades/spread_in_group', None) + if spread is not None: + spread_nodes = sorted(repo.nodes_in_group(spread)) + day += spread_nodes.index(rnode) + + downtimes.append({ + 'name': 'unattended-upgrades', + 'host': rnode.name, + 'comment': f'Downtime for upgrade-and-reboot of node {rnode.name}', + 'times': { + DAYS_TO_STRING[day%7]: f'{hour}:{minute}-{hour}:{minute+30}', + }, + }) + elif ( + rnode.has_bundle('pacman') + and rnode.metadata.get('pacman/unattended-upgrades/is_enabled', False) + ): + day = rnode.metadata.get('pacman/unattended-upgrades/day') + hour = rnode.metadata.get('pacman/unattended-upgrades/hour') + minute = rnode.magic_number%30 + + downtimes.append({ + 'name': 'unattended-upgrades', + 'host': rnode.name, + 'comment': f'Downtime for upgrade-and-reboot of node {rnode.name}', + 'times': { + DAYS_TO_STRING[day%7]: f'{hour}:{minute}-{hour}:{minute+30}', + }, + }) + files['/etc/icinga2/conf.d/groups.conf'] = { 'source': 'icinga2/groups.conf', 'content_type': 'mako', @@ -408,7 +444,7 @@ files['/etc/icinga2/conf.d/downtimes.conf'] = { 'source': 'icinga2/downtimes.conf', 'content_type': 'mako', 'context': { - 'days': DAYS_TO_STRING, + 'downtimes': downtimes, }, 'owner': 'nagios', 'group': 'nagios', diff --git a/groups/features.py b/groups/features.py index fca9379..8e20009 100644 --- a/groups/features.py +++ b/groups/features.py @@ -11,6 +11,11 @@ groups['dns'] = { 'powerdns', }, 'metadata': { + 'apt': { + 'unattended-upgrades': { + 'spread_in_group': 'dns', + }, + }, 'powerdns': { # Overridden in node metadata for primary server 'is_secondary': True, diff --git a/hooks/test_unattended_upgrades_spread.py b/hooks/test_unattended_upgrades_spread.py new file mode 100644 index 0000000..dbf87ce --- /dev/null +++ b/hooks/test_unattended_upgrades_spread.py @@ -0,0 +1,24 @@ +from bundlewrap.exceptions import BundleError +from bundlewrap.utils.text import bold, green, yellow +from bundlewrap.utils.ui import io + + +def test(repo, **kwargs): + for node in repo.nodes: + if not node.has_bundle('apt'): + continue + + spread = node.metadata.get('apt/unattended-upgrades/spread_in_group', None) + if spread is None: + continue + + for rnode in repo.nodes_in_group(spread): + rspread = rnode.metadata.get('apt/unattended-upgrades/spread_in_group', None) + + if spread != rspread: + raise BundleError(f'{node.name} sets apt/unattended-upgrades/spread_in_group to "{spread}", but node {rnode.name} in that group does set "{rspread}"!') + + io.stdout('{x} {node} apt/unattended-upgrades/spread_in_group matches'.format( + x=green("✓"), + node=bold(node.name), + ))