f-29: watchdog: add notify #29

Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
GnomeZworc 2026-08-17 23:06:08 +02:00
commit f05c4e1fce
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
3 changed files with 149 additions and 0 deletions

View file

@ -0,0 +1,5 @@
package notify
type Notifier interface {
Notify(kind, name, problem string)
}

View file

@ -0,0 +1,24 @@
package notify
import "log/slog"
var _ Notifier = (*StderrNotifier)(nil)
type StderrNotifier struct {
logger *slog.Logger
}
func NewStderr(l *slog.Logger) *StderrNotifier {
if l == nil {
l = slog.Default()
}
return &StderrNotifier{logger: l}
}
func (n *StderrNotifier) Notify(kind, name, problem string) {
n.logger.Error("watchdog: incohérence détectée",
"kind", kind,
"name", name,
"problem", problem,
)
}

View file

@ -0,0 +1,120 @@
package notify
import (
"bytes"
"encoding/json"
"log/slog"
"strings"
"testing"
)
func newTestNotifier(t *testing.T) (*StderrNotifier, *bytes.Buffer) {
t.Helper()
var buf bytes.Buffer
l := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelError}))
return NewStderr(l), &buf
}
func decodeLines(t *testing.T, buf *bytes.Buffer) []map[string]any {
t.Helper()
var out []map[string]any
for _, line := range strings.Split(strings.TrimSpace(buf.String()), "\n") {
if line == "" {
continue
}
var m map[string]any
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatalf("ligne non JSON %q: %v", line, err)
}
out = append(out, m)
}
return out
}
func TestNotify_EmetLesTroisChampsStructures(t *testing.T) {
n, buf := newTestNotifier(t)
n.Notify("vm", "i-test1", "tap12 absente du netns vp-admin")
lines := decodeLines(t, buf)
if len(lines) != 1 {
t.Fatalf("attendu 1 ligne, obtenu %d", len(lines))
}
for field, want := range map[string]string{
"kind": "vm",
"name": "i-test1",
"problem": "tap12 absente du netns vp-admin",
} {
got, ok := lines[0][field]
if !ok {
t.Errorf("champ %q absent de la sortie", field)
continue
}
if got != want {
t.Errorf("champ %q = %v, attendu %q", field, got, want)
}
}
}
func TestNotify_NiveauError(t *testing.T) {
n, buf := newTestNotifier(t)
n.Notify("vpc", "vp-admin", "netns absent")
lines := decodeLines(t, buf)
if len(lines) != 1 {
t.Fatalf("attendu 1 ligne, obtenu %d", len(lines))
}
if lines[0]["level"] != "ERROR" {
t.Errorf("level = %v, attendu ERROR", lines[0]["level"])
}
}
func TestNotify_MessageNeContientPasLesValeurs(t *testing.T) {
n, buf := newTestNotifier(t)
n.Notify("subnet", "br-000042", "bridge down")
lines := decodeLines(t, buf)
msg, _ := lines[0]["msg"].(string)
for _, v := range []string{"br-000042", "bridge down"} {
if strings.Contains(msg, v) {
t.Errorf("msg %q ne devrait pas contenir la valeur %q (elle doit être un attribut)", msg, v)
}
}
}
func TestNotify_AucuneDeduplication(t *testing.T) {
n, buf := newTestNotifier(t)
for range 3 {
n.Notify("vm", "i-test1", "qemu ne répond pas sur QMP")
}
if lines := decodeLines(t, buf); len(lines) != 3 {
t.Errorf("attendu 3 lignes (une par appel), obtenu %d", len(lines))
}
}
func TestNotify_ChampsVides(t *testing.T) {
n, buf := newTestNotifier(t)
n.Notify("", "", "")
if lines := decodeLines(t, buf); len(lines) != 1 {
t.Errorf("attendu 1 ligne, obtenu %d", len(lines))
}
}
func TestNewStderr_LoggerNil(t *testing.T) {
n := NewStderr(nil)
if n.logger == nil {
t.Fatal("logger nil non remplacé par slog.Default()")
}
n.Notify("vpc", "vp-admin", "netns absent")
}
func TestNewStderr_ImplementeNotifier(t *testing.T) {
var _ Notifier = NewStderr(nil)
}