From 15c9b772304e212296132fd759397c32a52af3a2 Mon Sep 17 00:00:00 2001 From: Marsell Kukuljevic Date: Thu, 25 Jun 2026 13:54:29 +0200 Subject: [PATCH] Improve VSPC policies support. --- .../libexec/agent_vspc_backup_checks | 96 +++++++++++++++--- .../2.3/vspc_backup_checks-0.4.0.mkp | Bin 5256 -> 0 bytes .../2.3/vspc_backup_checks-0.5.0.mkp | Bin 0 -> 6227 bytes 3 files changed, 83 insertions(+), 13 deletions(-) delete mode 100755 vspc_backup_checks/2.3/vspc_backup_checks-0.4.0.mkp create mode 100755 vspc_backup_checks/2.3/vspc_backup_checks-0.5.0.mkp diff --git a/vspc_backup_checks/2.3/local/lib/python3/cmk_addons/plugins/vspc_backup_checks/libexec/agent_vspc_backup_checks b/vspc_backup_checks/2.3/local/lib/python3/cmk_addons/plugins/vspc_backup_checks/libexec/agent_vspc_backup_checks index e58ccc9..d083213 100755 --- a/vspc_backup_checks/2.3/local/lib/python3/cmk_addons/plugins/vspc_backup_checks/libexec/agent_vspc_backup_checks +++ b/vspc_backup_checks/2.3/local/lib/python3/cmk_addons/plugins/vspc_backup_checks/libexec/agent_vspc_backup_checks @@ -12,6 +12,9 @@ CRIT = 2 SECONDS_PER_DAY = 24 * 60 * 60 +DAILY = "Daily" +WEEKLY = "Weekly" + # GET HTTP with Bearer auth. Returns data structure parsed from JSON. # @@ -121,7 +124,7 @@ def parse_arguments(): # ], # ... # } -def process(mAgents, bAgents, jobs, restores): +def process(mAgents, bAgents, jobs, restores, policies): mToB = {} for agent in bAgents: mToB[agent["managementAgentUid"]] = agent @@ -139,12 +142,12 @@ def process(mAgents, bAgents, jobs, restores): # for every (backup agent ID, job ID) pair. bToR = defaultdict(dict) for r in restores: - bId = r["backupAgentUid"] - jId = r["jobUid"] + bId = r["backupAgentUid"] + jId = r["jobUid"] - most_recent = bToR[bId].get(jId) - if not most_recent or most_recent["creationDate"] < r["creationDate"]: - bToR[bId][jId] = r + most_recent = bToR[bId].get(jId) + if not most_recent or most_recent["creationDate"] < r["creationDate"]: + bToR[bId][jId] = r results = defaultdict(list) for mAgent in mAgents: @@ -194,6 +197,7 @@ def process(mAgents, bAgents, jobs, restores): jobId = job["instanceUid"] last = job["lastEndTime"] sched = job["scheduleType"] + polId = job["backupPolicyUid"] daysSinceLastRun = None if last: @@ -222,12 +226,24 @@ def process(mAgents, bAgents, jobs, restores): warnDays = 0 critDays = 0 - if sched == "Daily": - # We use 1.2 & 2.2 here to give wiggle room for jobs to complete if - # they take longer than expected. - warnDays = 1.2 - critDays = 2.2 - elif sched == "Weekly": + if sched == DAILY: + policyType = policies.get(polId) + if policyType == WEEKLY: + # It may seem silly to check for WEEKLY under a DAILY section, + # but the scheduling allows for this, and we actually use this + # in prod due to backups being performed on a specific day in + # the week. + # + # We use 7.2 & 8.2 here to give wiggle room for jobs to + # complete if they take longer than expected. + warnDays = 7.2 + critDays = 8.2 + else: + # We use 1.2 & 2.2 here to give wiggle room for jobs to + # complete if they take longer than expected. + warnDays = 1.2 + critDays = 2.2 + elif sched == WEEKLY: # Ditto, but for a week warnDays = 7.2 critDays = 8.2 @@ -288,6 +304,54 @@ def process(mAgents, bAgents, jobs, restores): return results +# Alas, although VSPC's job data does specify whether a job runs "daily", it +# does not tell you which days. Veeam allows "daily" jobs to be configured to +# run on specific days of the week, which is something we use in production to +# run certain backup jobs only on a Saturday. +# +# So we need to filter through VSPC's somewhat inconsistent policy API to find +# daily settings. If a job is supposed to run on exactly one day a week, we +# treat it as weekly, otherwise daily. +# +# NB: if we ever use more than one day during the week (e.g. two), the logic in +# this plugin will need to be changed. The logic here, and in process() above, +# match what we currently do in prod, since a "better" approach will require a +# lot more code for a system we might phase out for something else. +def mergePolicies(linuxPolicies, windowsPolicies, macPolicies): + policies = {} + schedules = {} + + for policy in linuxPolicies + macPolicies: + schedule = policy["jobConfiguration"]["scheduleSettings"] + schedules[policy["instanceUid"]] = schedule + + for policy in windowsPolicies: + config = policy["jobConfiguration"] + + workstationSched = config.get("workstationModeSettings") + serverSched = config.get("serverModeSettings") + schedule = workstationSched or serverSched + if not schedule: + continue + schedule = schedule["scheduleSetting"] + schedule = schedule.get("periodicalScheduleSettings") or schedule + + schedules[policy["instanceUid"]] = schedule + + for polId, sched in schedules.items(): + dailySched = sched.get("dailyScheduleSettings") + if not dailySched: + continue + + days = dailySched.get("specificDays") + if days and len(days) == 1: + policies[polId] = WEEKLY + else: + policies[polId] = DAILY + + return policies + + def print_demo(): print(""" <<<>>> @@ -337,12 +401,18 @@ def main(argv=None): if args.demo: return print_demo() + linuxPolicies = get_paginated_json_url(args.hostname, args.port, '/api/v3/configuration/backupPolicies/linux', args.token, args.insecure) + windowsPolicies = get_paginated_json_url(args.hostname, args.port, '/api/v3/configuration/backupPolicies/windows', args.token, args.insecure) + macPolicies = get_paginated_json_url(args.hostname, args.port, '/api/v3/configuration/backupPolicies/mac', args.token, args.insecure) + mAgents = get_paginated_json_url(args.hostname, args.port, '/api/v3/infrastructure/managementAgents', args.token, args.insecure) bAgents = get_paginated_json_url(args.hostname, args.port, '/api/v3/infrastructure/backupAgents', args.token, args.insecure) jobs = get_paginated_json_url(args.hostname, args.port, '/api/v3/infrastructure/backupAgents/jobs', args.token, args.insecure) restores = get_paginated_json_url(args.hostname, args.port, '/api/v3/protectedWorkloads/computersManagedByConsole/restorePoints', args.token, args.insecure) - results = process(mAgents, bAgents, jobs, restores) + policies = mergePolicies(linuxPolicies, windowsPolicies, macPolicies) + + results = process(mAgents, bAgents, jobs, restores, policies) print_out(results) diff --git a/vspc_backup_checks/2.3/vspc_backup_checks-0.4.0.mkp b/vspc_backup_checks/2.3/vspc_backup_checks-0.4.0.mkp deleted file mode 100755 index 362eb09830248d42cda85ba463277cbf7adf2326..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5256 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 O%l`r2{Zm%}cmM$NhBFxe diff --git a/vspc_backup_checks/2.3/vspc_backup_checks-0.5.0.mkp b/vspc_backup_checks/2.3/vspc_backup_checks-0.5.0.mkp new file mode 100755 index 0000000000000000000000000000000000000000..700bd739b85bfc9bd0cfdb88f6a6a951e4358d45 GIT binary patch literal 6227 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 V{{P3^)iZi#7U(x82NVJT{|8x--faK? literal 0 HcmV?d00001