Hi Federico,
I found the solution with Claude:
Device stuck on Health status: "unknown" after being deactivated and
reactivated
Symptom
A device that was deactivated and then reactivated stays on Health
status: unknown, even though it's perfectly reachable. All checks
are green:
- Ping → reachable, 0% loss
- Configuration Applied → ok
- Monitoring Data Collected → data_collected = 1
- is_deactivated = False
Cause
In openwisp-monitoring, the device health status is only updated
when a metric crosses a threshold (the threshold_crossed signal). On
reactivation, the
handle_activated_device signal resets it:
cls.objects.filter(device_id=
instance.id).update(status="unknown")
From then on, if the metrics stay consistently healthy, there's no
crossing → no event → the status stays stuck on unknown even though
the device is ok. Same thing
happens if you disable/re-enable an organization, or remove a
critical check.
Fix
Recompute the status once with OpenWISP's own logic
(update_status): ok if no critical metric is unhealthy, otherwise
problem/critical. update_status() is idempotent
(no-op if the status is already correct) and skips deactivated
devices. Here's a reusable script:
#!/bin/sh
#
=============================================================================
# fix-monitoring-status.sh — recompute the health status in
openwisp-monitoring
#
# In openwisp-monitoring a device's status is only updated when a
metric crosses
# a threshold (threshold_crossed signal). If the status gets reset
to 'unknown'
# (device deactivated->reactivated, organization disabled,
critical check
# removed) and the metrics then stay consistently healthy, there
is no crossing
# and the status stays stuck on 'unknown' even though the device
is fine.
# This script recomputes the status with the SAME logic openwisp
uses:
# 'ok' if no critical metric is unhealthy, otherwise
'problem'/'critical'.
# update_status() is a no-op if the status is already correct.
#
# Run it ON THE SERVER where OpenWISP runs (default
/opt/openwisp2).
#
# Usage:
# ./fix-monitoring-status.sh # fix ALL 'unknown'
devices
# ./fix-monitoring-status.sh "Device Name" # fix a single
device
# DEVICE="Device Name" ./fix-monitoring-status.sh
#
# Env vars: OPENWISP_DIR (default /opt/openwisp2).
#
=============================================================================
set -e
OWDIR="${OPENWISP_DIR:-/opt/openwisp2}"
DEV="${1:-${DEVICE:-}}"
if [ ! -x "$OWDIR/env/bin/python" ]; then
echo "ERROR: OpenWISP not found in $OWDIR (set OPENWISP_DIR)"
>&2
exit 1
fi
cd "$OWDIR"
DEVICE="$DEV" ./env/bin/python manage.py shell <<'PY'
import os
from openwisp_monitoring.device.models import DeviceMonitoring
from openwisp_monitoring.device import settings as app_settings
def recompute(dm):
if dm.status == "deactivated":
print(f"{
dm.device.name}: deactivated (skipped)")
return
status, crit = "ok", 0
for m in dm.related_metrics.filter(is_healthy=False):
status = "problem"
if dm.is_metric_critical(m):
crit += 1
if crit == len(app_settings.CRITICAL_DEVICE_METRICS):
status = "critical"
old = dm.status
dm.update_status(status)
dm.refresh_from_db()
flag = "" if old == dm.status else " <-- changed"
print(f"{
dm.device.name}: {old} -> {dm.status}{flag}")
name = (os.environ.get("DEVICE") or "").strip()
if name:
qs = DeviceMonitoring.objects.filter(device__name=name)
if not qs:
raise SystemExit(f"Device '{name}' not found")
else:
qs = DeviceMonitoring.objects.filter(status="unknown")
print(f"Recomputing {qs.count()} device(s) in 'unknown'
state...")
for dm in qs:
recompute(dm)
PY
Run it:
# fix all 'unknown' devices at once
./fix-monitoring-status.sh
# or a single device
DEVICE="my-device" ./fix-monitoring-status.sh
# remotely, piping it to the server over SSH
ssh user@server 'sh -s' < fix-monitoring-status.sh
ssh user@server 'DEVICE="my-device" sh -s' <
fix-monitoring-status.sh
This doesn't blindly force ok — a genuinely unhealthy device
becomes problem/critical; it just re-evaluates the state that never
got recomputed because no threshold was
crossed.