software steht so weit.
jetzt build + docker build und testen
This commit is contained in:
1 parent
42f0c0accb
commit
ca6cdcca63
90 files changed
+10298
-480
No files matched your search
@@ -0,0 +1,137 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
tagRe = regexp.MustCompile(`(?s)<[^>]*>`)
|
||||
spaceRe = regexp.MustCompile(`\s+`)
|
||||
)
|
||||
|
||||
// checkHealth ruft spec.URL ab und prüft Status sowie optional <title> und
|
||||
// ein Element mit Text. Bewusst einfacher Textabgleich statt HTML-Parser:
|
||||
// Es geht um "liefert die Seite noch das Erwartete", nicht um Validierung.
|
||||
func checkHealth(ctx context.Context, client *http.Client, spec HealthSpec) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, spec.Timeout.Duration)
|
||||
defer cancel()
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, spec.URL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != spec.ExpectStatus {
|
||||
return fmt.Errorf("Status %d statt %d", resp.StatusCode, spec.ExpectStatus)
|
||||
}
|
||||
if spec.ExpectTitle == "" && spec.ExpectContains == nil {
|
||||
return nil
|
||||
}
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if spec.ExpectTitle != "" {
|
||||
titles := elementTexts(string(body), "title")
|
||||
if len(titles) == 0 || titles[0] != strings.TrimSpace(spec.ExpectTitle) {
|
||||
return fmt.Errorf("<title> %q statt %q", strings.Join(titles, ""), spec.ExpectTitle)
|
||||
}
|
||||
}
|
||||
if c := spec.ExpectContains; c != nil {
|
||||
for _, text := range elementTexts(string(body), c.Tag) {
|
||||
if strings.Contains(text, c.Text) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("kein <%s> mit %q", c.Tag, c.Text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// elementTexts liefert den Textinhalt aller <tag>-Elemente (ohne innere Tags,
|
||||
// Leerraum zusammengefasst). Groß-/Kleinschreibung des Tags ist egal.
|
||||
func elementTexts(html, tag string) []string {
|
||||
t := regexp.QuoteMeta(tag)
|
||||
re := regexp.MustCompile(`(?is)<` + t + `\b[^>]*>(.*?)</` + t + `\s*>`)
|
||||
var out []string
|
||||
for _, m := range re.FindAllStringSubmatch(html, -1) {
|
||||
text := tagRe.ReplaceAllString(m[1], " ")
|
||||
out = append(out, strings.TrimSpace(spaceRe.ReplaceAllString(text, " ")))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// healthTracker merkt sich den Zustand je Dienst: "down" erst nach
|
||||
// threshold Fehlschlägen in Folge, "up" nach einem Erfolg.
|
||||
type healthTracker struct {
|
||||
mu sync.Mutex
|
||||
state map[string]*healthState
|
||||
}
|
||||
|
||||
type healthState struct {
|
||||
fails int
|
||||
down bool
|
||||
}
|
||||
|
||||
// report verbucht ein Ergebnis und meldet, ob sich up/down geändert hat.
|
||||
func (t *healthTracker) report(name string, threshold int, ok bool) bool {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.state == nil {
|
||||
t.state = map[string]*healthState{}
|
||||
}
|
||||
st := t.state[name]
|
||||
if st == nil {
|
||||
st = &healthState{}
|
||||
t.state[name] = st
|
||||
}
|
||||
if ok {
|
||||
st.fails = 0
|
||||
if st.down {
|
||||
st.down = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
st.fails++
|
||||
if !st.down && st.fails >= threshold {
|
||||
st.down = true
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// down liefert die ausgefallenen Dienste, sortiert (nil, wenn keiner).
|
||||
func (t *healthTracker) down() []string {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
var out []string
|
||||
for name, st := range t.state {
|
||||
if st.down {
|
||||
out = append(out, name)
|
||||
}
|
||||
}
|
||||
slices.Sort(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// retain vergisst Dienste, die es nicht mehr gibt.
|
||||
func (t *healthTracker) retain(keep map[string]bool) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
for name := range t.state {
|
||||
if !keep[name] {
|
||||
delete(t.state, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user