112 lines
4.6 KiB
Go
112 lines
4.6 KiB
Go
// Package heard is the finding a part of mesh-delivery writes while one of its consumers or watches does not hear the
|
|
// family it follows (novox/hq ADR 0615 §5; review round 1 of the registry milestone): the consumer not there yet, a
|
|
// hearing that could not start, a watch that cannot be opened. Without it the part's health stays healthy and its
|
|
// status says answered while the events it waits on are lost to it. The finding names the observer and the family,
|
|
// says what is not heard and why, and is deleted when the consumer binds (at the part's next start, or when it opens
|
|
// later); the condition keeper raises and clears the condition from it (ADR 0564 §2).
|
|
package heard
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
)
|
|
|
|
// Kind is the finding's kind.
|
|
const Kind = "family-not-heard"
|
|
|
|
// ID is the finding's subject: `<observer>.<family>`, the family by the name the part follows it under (a stream, an
|
|
// event, a state's watch).
|
|
func ID(observer, family string) string { return observer + "." + family }
|
|
|
|
// Key is the finding's key in the observer's findings state.
|
|
func Key(observer, family string) string {
|
|
return health.FindingKey(health.ScopeModule, ID(observer, family), Kind)
|
|
}
|
|
|
|
// Words are the finding's words: its summary, headline and explanation. consequence says what the observer misses while
|
|
// it does not hear family ("the placement is the first view's alone").
|
|
func Words(observer, family, consequence string, why error) (summary, headline, explanation string) {
|
|
reason := "it gave no reason"
|
|
if why != nil {
|
|
reason = strings.TrimSpace(why.Error())
|
|
}
|
|
summary = fmt.Sprintf("%s does not hear %s: %s (%s)", observer, family, consequence, reason)
|
|
headline = fmt.Sprintf("%s does not hear %s", observer, family)
|
|
explanation = fmt.Sprintf("%s follows %s on a consumer of its own, and that consumer is not there or could not be heard, so %s. "+
|
|
"It is heard again once the consumer binds, at %s's next start or when the grants part has made it; this clears then. "+
|
|
"Look at whether the grants part made %s's consumers.", observer, family, consequence, observer, observer)
|
|
return summary, headline, explanation
|
|
}
|
|
|
|
// Finding is observer's finding that it does not hear family, as health.Finding encodes it.
|
|
func Finding(observer, family, consequence string, why error, at time.Time) (string, []byte, error) {
|
|
key := Key(observer, family)
|
|
summary, headline, explanation := Words(observer, family, consequence, why)
|
|
value, err := health.EncodeFinding(health.Finding{Key: key, Kind: Kind, Subject: health.Subject{Scope: health.ScopeModule, ID: ID(observer, family)},
|
|
Severity: health.Warning, Summary: summary, Headline: headline, Explanation: explanation,
|
|
Evidence: []health.Evidence{{At: at.UTC(), Said: summary}}, Source: observer + "." + Kind, Observer: observer, At: at.UTC()})
|
|
return key, value, err
|
|
}
|
|
|
|
// Writer writes and deletes the finding in the observer's findings state. Put or Delete nil writes nothing, said in the
|
|
// log, so the part still says it.
|
|
type Writer struct {
|
|
Observer string
|
|
Put func(key string, value []byte) error
|
|
Delete func(key string) error
|
|
Logf func(string, ...any)
|
|
Now func() time.Time
|
|
}
|
|
|
|
// Bind binds the observer's consumer of family (bind makes it and starts hearing it): the finding is deleted when it
|
|
// binds, and written, with why, when it does not. It answers bind's error, so a caller that cannot go on without the
|
|
// family still stops.
|
|
func (w Writer) Bind(family, consequence string, bind func() error) error {
|
|
if err := bind(); err != nil {
|
|
w.NotHeard(family, consequence, err)
|
|
return err
|
|
}
|
|
w.Heard(family)
|
|
return nil
|
|
}
|
|
|
|
// NotHeard says in the log and in the finding that the observer does not hear family.
|
|
func (w Writer) NotHeard(family, consequence string, why error) {
|
|
now := time.Now
|
|
if w.Now != nil {
|
|
now = w.Now
|
|
}
|
|
summary, _, _ := Words(w.Observer, family, consequence, why)
|
|
w.logf("%s", summary)
|
|
if w.Put == nil {
|
|
w.logf("the finding %s is not written: no findings state", Key(w.Observer, family))
|
|
return
|
|
}
|
|
key, value, err := Finding(w.Observer, family, consequence, why, now())
|
|
if err == nil {
|
|
err = w.Put(key, value)
|
|
}
|
|
if err != nil {
|
|
w.logf("the finding %s is not written: %v", key, err)
|
|
}
|
|
}
|
|
|
|
// Heard deletes the finding: the consumer of family is bound.
|
|
func (w Writer) Heard(family string) {
|
|
if w.Delete == nil {
|
|
return
|
|
}
|
|
if err := w.Delete(Key(w.Observer, family)); err != nil {
|
|
w.logf("the finding %s is not deleted: %v", Key(w.Observer, family), err)
|
|
}
|
|
}
|
|
|
|
func (w Writer) logf(format string, args ...any) {
|
|
if w.Logf != nil {
|
|
w.Logf(format, args...)
|
|
}
|
|
}
|