f8fc09e5cc
store.GetHostOOBStates parses the agent oob heartbeat stanza. monitor/host_oob: transition-based oob_degraded/oob_recovered warning (felhom-sshd down while the operator peer is configured, OR config invalid) — proactive "can the operator get in right now" signal; unconfigured OOB never alerts. Wired into the 60s sweep. Non-hollow tests + transitions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
60 lines
1.9 KiB
Go
60 lines
1.9 KiB
Go
package store
|
|
|
|
import "encoding/json"
|
|
|
|
// HostOOBRow is the latest operator-access (OOB) state per host (TASK H1), parsed from the newest
|
|
// host_report. Present is false when the agent sent no oob stanza (pre-H1 / feature off) → never
|
|
// alerted.
|
|
type HostOOBRow struct {
|
|
HostID string
|
|
CustomerID string
|
|
Present bool
|
|
FelhomSshdActive bool
|
|
FelhomSshdPort int
|
|
Reachable bool
|
|
ConfigInvalid bool
|
|
OperatorPeerConfigured bool
|
|
}
|
|
|
|
// GetHostOOBStates returns the latest oob stanza per host (mirrors GetHostMgmtPlaneStates). A report
|
|
// without the stanza yields Present=false; malformed JSON degrades to zero values, never an error.
|
|
func (s *Store) GetHostOOBStates() ([]HostOOBRow, error) {
|
|
rows, err := s.db.Query(`
|
|
SELECT hr.host_id, hr.customer_id, hr.report_json
|
|
FROM host_reports hr
|
|
JOIN (SELECT host_id, MAX(id) AS mx FROM host_reports GROUP BY host_id) latest
|
|
ON hr.id = latest.mx`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []HostOOBRow
|
|
for rows.Next() {
|
|
var r HostOOBRow
|
|
var reportJSON string
|
|
if err := rows.Scan(&r.HostID, &r.CustomerID, &reportJSON); err != nil {
|
|
return nil, err
|
|
}
|
|
var body struct {
|
|
OOB *struct {
|
|
FelhomSshdActive bool `json:"felhom_sshd_active"`
|
|
FelhomSshdPort int `json:"felhom_sshd_port"`
|
|
Reachable bool `json:"reachable"`
|
|
ConfigInvalid bool `json:"config_invalid"`
|
|
OperatorPeerConfigured bool `json:"operator_peer_configured"`
|
|
} `json:"oob"`
|
|
}
|
|
_ = json.Unmarshal([]byte(reportJSON), &body)
|
|
if body.OOB != nil {
|
|
r.Present = true
|
|
r.FelhomSshdActive = body.OOB.FelhomSshdActive
|
|
r.FelhomSshdPort = body.OOB.FelhomSshdPort
|
|
r.Reachable = body.OOB.Reachable
|
|
r.ConfigInvalid = body.OOB.ConfigInvalid
|
|
r.OperatorPeerConfigured = body.OOB.OperatorPeerConfigured
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
return out, rows.Err()
|
|
}
|