From d5d964ea8e9390676dbf7e67b8859647db50362a Mon Sep 17 00:00:00 2001 From: Yannik Schmidt Date: Wed, 3 Jan 2024 13:40:42 +0100 Subject: [PATCH] wip: --- server.py | 27 +++++++++++++++++--- smarttools.py | 49 +++++++++++++++++++++++++++++++++++++ templates/service_info.html | 15 ++++++++++++ 3 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 smarttools.py diff --git a/server.py b/server.py index 6151a92..deef0be 100755 --- a/server.py +++ b/server.py @@ -24,6 +24,7 @@ from flask_sqlalchemy import SQLAlchemy from sqlalchemy.sql.expression import func import icingatools +import smarttools app = flask.Flask("Icinga Report In Gateway") @@ -72,6 +73,7 @@ class SMARTStatus(db.Model): model_number = Column(String) power_cycles = Column(Integer) power_on_hours = Column(Integer) + wearleveling_count = Column(Integer) def buildReponseDict(status, service=None): @@ -311,6 +313,13 @@ def default(): smart = flask.request.json.get("smart") + # check smart json quoting problems # + if smart and type(smart) == str: + try: + smart = json.loads(smart) + except json.decoder.JSONDecodeError as e: + return ("Error in SMART-json {}".format(e), 415) + if not service: return ("'service' ist empty field in json", 400) elif not token: @@ -343,7 +352,10 @@ def default(): def record_and_check_smart(service, timestamp, smart): - health_info = smart["nvme_smart_health_information_log"] + if "nvme_smart_health_information_log" in smart: + health_info = smart["nvme_smart_health_information_log"] + else: + health_info = smarttools.normalize(smart) if not service.special_type == "SMART": raise AssertionError("Trying to record SMART-record for non-SMART service") @@ -356,7 +368,8 @@ def record_and_check_smart(service, timestamp, smart): power_cycles=health_info["power_cycles"], power_on_hours=health_info["power_on_hours"], available_spare=health_info.get("available_spare"), - model_number=smart.get("model_name")) + model_number=smart.get("model_name"), + wearleveling_count=health_info.get("wearleveling_count")) db.session.add(smart_status) db.session.commit() @@ -378,6 +391,10 @@ def record_and_check_smart(service, timestamp, smart): if smart_last.critical_warning != 0: return ("SMART reports disk critical => oO better do something about this", "CRITICAL") + # wearleveling < 20% (SAMSUNG only) # + if smart_last.wearleveling_count and smart_last.wearleveling_count <= 20: + return ("SMART report prefail disk (wear_level < 20%)", "CRITICAL") + # temp max > X # if smart_last.temperature > 50: return ("Disk Temperatur {}".format(smart_last.temperature), "CRITICAL") @@ -386,7 +403,8 @@ def record_and_check_smart(service, timestamp, smart): spare_change = smart_old.available_spare - smart_last.available_spare if smart_last.available_spare <= 25: - return ("SSD spare <25 ({}) YOUR DISK WILL DIE SOON".format(spare_change), "CRITICAL") + return ("SSD spare <25 ({}) YOUR DISK WILL DIE SOON".format(spare_change), + "CRITICAL") elif smart_last.available_spare <= 50: return ("SSD spare <50 ({})".format(spare_change), "WARNING") elif spare_change >= 10: @@ -395,7 +413,8 @@ def record_and_check_smart(service, timestamp, smart): # unsafe_shutdowns +1 # if smart_second_last.unsafe_shutdowns - smart_last.unsafe_shutdowns >= 1: - return ("Disk had {} unsafe shutdowns".format(smart_last.unsafe_shutdowns), "WARNING") + return ("Disk had {} unsafe shutdowns".format(smart_last.unsafe_shutdowns), + "WARNING") return ("", "OK") diff --git a/smarttools.py b/smarttools.py new file mode 100644 index 0000000..50dfd41 --- /dev/null +++ b/smarttools.py @@ -0,0 +1,49 @@ +def normalize(smart): + '''Load different types of SMART outputs''' + + ret = dict() + ret.update({ "temperature" : 0 }) + ret.update({ "critical_warning" : 0 }) + ret.update({ "unsafe_shutdowns" : 0 }) + ret.update({ "power_cycles" : 0 }) + ret.update({ "power_on_hours" : 0 }) + ret.update({ "available_spare" : 100 }) + ret.update({ "wearleveling_count" : 100 }) + + if "ata_smart_attributes" in smart: + + # get main table # + table = smart["ata_smart_attributes"]["table"] + + # temperatur # + ret["temperature"] = smart["temperature"]["current"] + + for el in table: + + # look for relevant metrics # + name = el["name"].lower() + target_name = el["name"].lower() # name in return map + + # handle value mapping # + use_raw = False + if name == "used_rsvd_blk_cnt_tot": + target_name = "available_spare" + elif name == "power_cylce_count": + target_name = "power_cycles" + use_raw = True + elif name == "power_on_hours": + target_name = "power_on_hours" + use_raw = True + + # check if metric should be recorded # + if target_name in ret: + + # set return dict # + if use_raw: + value = el["raw"]["value"] + else: + value = el["value"] + + ret[target_name] = value + + return ret diff --git a/templates/service_info.html b/templates/service_info.html index f7b4ba2..f82eed5 100644 --- a/templates/service_info.html +++ b/templates/service_info.html @@ -115,6 +115,20 @@ {% endif %} + {% if smart %} +
Windows
+
+ $SMART = @{
+
+ service = "{{ service.service }}"
+ token = "{{ service.token }}"
+ status = "N/A"
+ smart = "$(smartctl -a C: --json | Out-String)"
+
+ } | ConvertTo-Json

+ Invoke-RestMethod -TimeoutSec 2 -Uri "{{ flask.request.url_root.replace("http://", "https://" )}}report" -Method Post -Headers @{"Content-Type"="application/json"} -Body $SMART +
+ {% else %}
Python
import requests
@@ -129,6 +143,7 @@
+ {% endif %}