128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
TSM Backup Status Agent Plugin for CheckMK
|
|
- Reads CSV files from /mnt/CMK_TSM
|
|
- Normalizes RRZ*/NFRZ* node names for redundancy
|
|
- Aggregates backups per logical node
|
|
- Outputs data as CheckMK agent section
|
|
|
|
Installation: /usr/lib/check_mk_agent/plugins/tsm_backups
|
|
Permissions: chmod +x tsm_backups
|
|
|
|
Author: Marius Gielnik
|
|
Version: 1.0 - Agent Plugin for CheckMK 2.3+
|
|
"""
|
|
import csv
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
import re
|
|
import sys
|
|
|
|
CSV_DIR = Path("/mnt/CMK_TSM")
|
|
|
|
class TSMParser:
|
|
def __init__(self):
|
|
self.backups = []
|
|
|
|
def normalize_node_name(self, node):
|
|
"""Normalisiert Node-Namen für Redundanz-Logik"""
|
|
pattern = r'(RRZ|NFRZ|RZ)\d+(_)'
|
|
normalized = re.sub(pattern, r'\2', node)
|
|
pattern_end = r'(RRZ|NFRZ|RZ)\d+$'
|
|
normalized = re.sub(pattern_end, '', normalized)
|
|
return normalized
|
|
|
|
def is_valid_node(self, node, status):
|
|
"""Prüft ob ein Node valide ist"""
|
|
if not node or len(node) < 3 or not status:
|
|
return False
|
|
if "MAINTENANCE" in node:
|
|
return False
|
|
return True
|
|
|
|
def parse_csv(self, csv_file):
|
|
"""Parst CSV-Datei"""
|
|
try:
|
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
|
reader = csv.reader(f)
|
|
for row in reader:
|
|
if not row or len(row) < 5:
|
|
continue
|
|
|
|
time_str = row[0].strip()
|
|
node = row[2].strip()
|
|
schedule = row[3].strip()
|
|
status = row[4].strip()
|
|
|
|
if not self.is_valid_node(node, status):
|
|
continue
|
|
|
|
normalized_node = self.normalize_node_name(node)
|
|
|
|
self.backups.append({
|
|
"time": time_str,
|
|
"node": normalized_node,
|
|
"status": status,
|
|
"schedule": schedule,
|
|
})
|
|
except Exception:
|
|
pass
|
|
|
|
def aggregate(self):
|
|
"""Aggregiert Backups pro logischem Node"""
|
|
nodes = defaultdict(lambda: {
|
|
"statuses": [],
|
|
"schedules": [],
|
|
"last": None,
|
|
"count": 0,
|
|
})
|
|
|
|
for b in self.backups:
|
|
node = b["node"]
|
|
nodes[node]["count"] += 1
|
|
nodes[node]["statuses"].append(b["status"])
|
|
nodes[node]["schedules"].append(b["schedule"])
|
|
|
|
try:
|
|
t = datetime.strptime(b["time"], "%Y-%m-%d %H:%M:%S")
|
|
if not nodes[node]["last"] or t > nodes[node]["last"]:
|
|
nodes[node]["last"] = t
|
|
except Exception:
|
|
pass
|
|
|
|
# Convert datetime to timestamp for JSON serialization
|
|
result = {}
|
|
for node, data in nodes.items():
|
|
result[node] = {
|
|
"statuses": data["statuses"],
|
|
"schedules": data["schedules"],
|
|
"last": int(data["last"].timestamp()) if data["last"] else None,
|
|
"count": data["count"]
|
|
}
|
|
return result
|
|
|
|
def main():
|
|
if not CSV_DIR.exists():
|
|
# Output empty section if directory doesn't exist
|
|
print("<<<tsm_backups:sep(0)>>>")
|
|
print(json.dumps({}))
|
|
return
|
|
|
|
csv_files = list(CSV_DIR.glob("*_SCHED_24H.CSV"))
|
|
if not csv_files:
|
|
csv_files = list(CSV_DIR.glob("*.CSV")) + list(CSV_DIR.glob("*.csv"))
|
|
|
|
parser = TSMParser()
|
|
for csv_file in csv_files:
|
|
parser.parse_csv(csv_file)
|
|
|
|
nodes = parser.aggregate()
|
|
|
|
# Output CheckMK agent section
|
|
print("<<<tsm_backups:sep(0)>>>")
|
|
print(json.dumps(nodes))
|
|
|
|
if __name__ == "__main__":
|
|
main() |