mesh-self-check: D13 raises empty-replacement and cleanup-waiting for retired readings and counts a missing backup from the first measurement; sdk 31e90aa (issue 1952, ADR 0233)
This commit is contained in:
@@ -42,6 +42,7 @@ const (
|
||||
kindProtectionMissing = "protection-missing"
|
||||
kindCleanupWaiting = "cleanup-waiting"
|
||||
shrinkWindowDays = 7
|
||||
cleanupAfter = 30 * 24 * time.Hour
|
||||
shrinkFloor int64 = 16 << 20
|
||||
)
|
||||
|
||||
@@ -194,9 +195,12 @@ func sizeWords(size *int64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// firstSeen is when an item was first read: the oldest day its seven-day peak is made of, else its measurement. A lower
|
||||
// bound on how long it has been declared (the controller kept the day it first saw it).
|
||||
// firstSeen is when an item was first measured on its node (the reading keeps it); for a reading written before it did,
|
||||
// the oldest day its seven-day peak is made of, else its measurement: a lower bound on how long it has been declared.
|
||||
func firstSeen(r health.Reading, now time.Time) time.Time {
|
||||
if !r.FirstSeen.IsZero() {
|
||||
return r.FirstSeen
|
||||
}
|
||||
if len(r.Days) > 0 {
|
||||
if t, err := time.Parse("2006-01-02", r.Days[0].Day); err == nil {
|
||||
return t
|
||||
@@ -223,6 +227,8 @@ func dataFindings(f dataFacts) []Observation {
|
||||
items []string
|
||||
}
|
||||
shrunkDatasets := map[string]shrunk{}
|
||||
byItem := map[string][]health.Reading{}
|
||||
retiredCopies := map[string][]health.Reading{}
|
||||
for _, m := range f.unmeasured {
|
||||
o := obs(kindDataUnmeasured, m, m, Warning, fmt.Sprintf("%s's backup holder did not say what it measured of the data "+
|
||||
"declared there, so nothing about that data is known", m), "disk-load keeps no reading of this machine's data")
|
||||
@@ -237,6 +243,19 @@ func dataFindings(f dataFacts) []Observation {
|
||||
class := r.Class
|
||||
item, declared := f.manifests[r.Module].DataItem(r.ID)
|
||||
id := keyOfItem(r.Node, r.Module, r.ID)
|
||||
if !r.RetiredAt.IsZero() {
|
||||
retiredCopies[r.Module+"/"+r.ID] = append(retiredCopies[r.Module+"/"+r.ID], r)
|
||||
if f.now.Sub(r.RetiredAt) > cleanupAfter && catalogue.Watched(class) {
|
||||
out = append(out, obs(kindCleanupWaiting, id, r.Node, Warning,
|
||||
fmt.Sprintf("%s of %s on %s (%s, %s) has been retired %d days — kept where it was since %s; "+
|
||||
"`cleanup delete %s %s %s --why …` once a person has decided, or assign %s there again",
|
||||
r.ID, r.Module, r.Node, r.Class, sizeWords(r.SizeBytes), int(f.now.Sub(r.RetiredAt).Hours()/24),
|
||||
r.RetiredWhy, r.Node, r.Module, r.ID, r.Module), "kept at "+orUnknownPath(r.Path)))
|
||||
out[len(out)-1].Token = "cleanup"
|
||||
}
|
||||
continue
|
||||
}
|
||||
byItem[r.Module+"/"+r.ID] = append(byItem[r.Module+"/"+r.ID], r)
|
||||
if !catalogue.Watched(class) {
|
||||
continue
|
||||
}
|
||||
@@ -338,5 +357,36 @@ func dataFindings(f dataFacts) []Observation {
|
||||
machine, severityOf(class), fmt.Sprintf("the %s storage on %s that protects %s %s", red.Kind, machine,
|
||||
strings.Join(names, ", "), state), fmt.Sprintf("the %s storage %s %s: %s", red.Kind, red.Where, state, firstLine(red.Said))))
|
||||
}
|
||||
// The same item on several machines: a copy that is in use and far smaller than one kept retired elsewhere is an
|
||||
// empty replacement. Only against a retired copy — a module running on two machines on purpose keeps two different
|
||||
// sets of data.
|
||||
items := make([]string, 0, len(byItem))
|
||||
for k := range byItem {
|
||||
items = append(items, k)
|
||||
}
|
||||
sort.Strings(items)
|
||||
for _, key := range items {
|
||||
for _, a := range byItem[key] {
|
||||
if a.SizeBytes == nil || !catalogue.Watched(a.Class) || !comparable(a.Precision) {
|
||||
continue
|
||||
}
|
||||
for _, o := range retiredCopies[key] {
|
||||
if o.Node == a.Node || o.SizeBytes == nil || !comparable(o.Precision) || !replacedByLess(*a.SizeBytes, *o.SizeBytes) {
|
||||
continue
|
||||
}
|
||||
ob := obs(kindEmptyReplacement, keyOfItem(a.Node, a.Module, a.ID), a.Node, severityOf(a.Class),
|
||||
fmt.Sprintf("%s of %s on %s holds %s, and the copy %s kept on %s holds %s: %s is running on "+
|
||||
"an empty replacement of its data. Move the data, or assign it back where its data is",
|
||||
a.ID, a.Module, a.Node, sizeWords(a.SizeBytes), o.Module, o.Node, sizeWords(o.SizeBytes), a.Module), "")
|
||||
ob.Subject.Also = []string{o.Node}
|
||||
out = append(out, ob)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// replacedByLess is whether a copy in use is an empty replacement of a copy kept elsewhere: less than half of it, and at
|
||||
// least shrinkFloor less.
|
||||
func replacedByLess(inUse, kept int64) bool { return inUse*2 < kept && kept-inUse >= shrinkFloor }
|
||||
|
||||
@@ -187,6 +187,22 @@ func TestTheBusProbes(t *testing.T) {
|
||||
r.size("ace", "plex", "thumbs", 10<<20, 600<<20).dataset("ace", "plex", "thumbs", "storage/media")
|
||||
r.size("ace", "plex", "meta", 10<<20, 600<<20).dataset("ace", "plex", "meta", "storage/media")
|
||||
})},
|
||||
{"D13 a retired item is kept, not judged, and cleanup waits after thirty days", probeData, bus{}, []string{
|
||||
"machine.ace.pg.dumps.cleanup"}, "", dataMesh(func(r *dataRig) {
|
||||
r.size("ace", "pg", "dumps", 1<<20, 1<<30).retired("ace", "pg", "dumps", -31*24*time.Hour)
|
||||
r.size("ace", "pg", "old", 1<<20, 1<<30).retired("ace", "pg", "old", -2*24*time.Hour)
|
||||
})},
|
||||
{"D13 a copy far smaller than a retired one elsewhere is an empty replacement", probeData, bus{},
|
||||
[]string{"machine.ace.plex.thumbs.empty-replacement"}, "", dataMesh(func(r *dataRig) {
|
||||
r.size("ace", "plex", "thumbs", 10<<20, 10<<20)
|
||||
r.size("g14", "plex", "thumbs", 600<<20, 600<<20).retired("g14", "plex", "thumbs", -24*time.Hour)
|
||||
})},
|
||||
{"D13 no good backup is counted from when the item was first measured", probeData, bus{},
|
||||
[]string{"machine.ace.pg.data.backup-stale"}, "", dataMesh(func(r *dataRig) {
|
||||
delete(r.good, health.ReadingKey("ace", "pg", "data"))
|
||||
delete(r.good, health.ReadingKey("ace", "plex", "meta"))
|
||||
r.firstSeen("ace", "pg", "data", -100*time.Hour).firstSeen("ace", "plex", "meta", -time.Hour)
|
||||
})},
|
||||
{"D13 a machine with a backup holder and no reading", probeData, bus{}, []string{"machine.g14.data-unmeasured"}, "",
|
||||
dataMesh(func(r *dataRig) { r.holder("g14") })},
|
||||
// D-fill (ADR 0297 §6): a module's bucket or log at three quarters of its cap, kept until under seven tenths.
|
||||
@@ -375,6 +391,17 @@ func (r *dataRig) lastGood(node, module, item string, ago time.Duration) *dataRi
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *dataRig) retired(node, module, item string, ago time.Duration) *dataRig {
|
||||
x := r.reading(node, module, item)
|
||||
x.RetiredAt, x.RetiredWhy = dataNow.Add(ago), "no longer declared on "+node
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *dataRig) firstSeen(node, module, item string, ago time.Duration) *dataRig {
|
||||
r.reading(node, module, item).FirstSeen = dataNow.Add(ago)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *dataRig) holder(node string) {
|
||||
key, _ := delivery.SeatHoldersKey("node-backup", node)
|
||||
_ = r.s.Put(SeatHoldersRead, key, delivery.SeatHolders{V: 1, Seat: "node-backup", Scope: "node", Node: node,
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-self-check
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029224808-f3677eb6f36e
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029225606-31e90aaa0f0d
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029224808-f3677eb6f36e h1:h8bh7ZyAgPuFC9/lc6iFBD/hduzomLodZ0a1oJZ5X4U=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029224808-f3677eb6f36e/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029225606-31e90aaa0f0d h1:HHSXxRS+GEYNgtdg9MFB8W2Y6ycPJF7YLF46y3oj7Ow=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261029225606-31e90aaa0f0d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
Reference in New Issue
Block a user