Assigner: the integration test of wants on the bus proves a running node's first want and the neither condition; held-condition reads filter by key (issue 1945, try-1792937443523744996)

This commit is contained in:
2026-10-25 15:18:07 +01:00
parent 8daa5977f9
commit f7b23033a2
@@ -281,9 +281,11 @@ func (b *bed) get(t *testing.T, bucket, key string) []byte {
return nil return nil
} }
// 1297/B4 on the bus, 1906/D5: the controller sends ace and g14 builds while the canary ace does not run F healthy yet. // 1297/B4 on the bus, 1906/D5: the controller sends ace, g14 and shanks builds while the canary ace does not run F healthy yet.
// Started whole, the assigner wants F on the canary, holds g14 back with no want, and writes no difference at all: there // Started whole, the assigner wants F on the canary and writes no difference at all: there is no send to compare with (ADR 0539
// is no send to compare with (ADR 0539 §8). // §8). 1945/AC19, AC21: shanks, which runs the module with no want, gets its first want past the canary rule; g14, which does not
// run it, is held back with no want, so web is on neither list there and the assigner raises the condition naming g14, web and
// the canary rule.
func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) { func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
b := startBed(t, "") b := startBed(t, "")
fNew, fOld := delivery.SourcePrefix2+strings.Repeat("b", 64), delivery.SourcePrefix2+strings.Repeat("a", 64) fNew, fOld := delivery.SourcePrefix2+strings.Repeat("b", 64), delivery.SourcePrefix2+strings.Repeat("a", 64)
@@ -299,16 +301,16 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
gkey, _ := delivery.ControllerGrantKey("web", fNew) gkey, _ := delivery.ControllerGrantKey("web", fNew)
grant, _ := delivery.EncodeControllerGrant(delivery.ControllerGrant{}) grant, _ := delivery.EncodeControllerGrant(delivery.ControllerGrant{})
b.putJSON(t, delivery.ControllerGrantsBucket, gkey, grant) b.putJSON(t, delivery.ControllerGrantsBucket, gkey, grant)
b.compose(t, "ace", "web", fNew, 'c', delivery.JudgedNeither) for _, node := range []string{"ace", "g14", "shanks"} {
b.compose(t, "g14", "web", fNew, 'c', delivery.JudgedNeither) b.compose(t, node, "web", fNew, 'c', delivery.JudgedNeither)
for _, node := range []string{"ace", "g14"} {
on, _ := delivery.EncodePresence(delivery.Presence{Online: true}) on, _ := delivery.EncodePresence(delivery.Presence{Online: true})
b.putJSON(t, delivery.PresenceBucket, node, on) b.putJSON(t, delivery.PresenceBucket, node, on)
have, _ := delivery.EncodeHave(delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK}) if node != "g14" {
b.putJSON(t, delivery.HaveBucket, node+".web", have) have, _ := delivery.EncodeHave(delivery.Have{FP: fOld, Phase: delivery.PhaseRunning, Health: delivery.HealthOK})
b.putJSON(t, delivery.HaveBucket, node+".web", have)
}
b.putJSON(t, provider.AssignmentsBucket, node+".web", []byte(`{"node":"`+node+`","module":"web","build":"b-`+node+`"}`))
} }
b.putJSON(t, provider.AssignmentsBucket, "ace.web", []byte(`{"node":"ace","module":"web","build":"b-8"}`))
b.putJSON(t, provider.AssignmentsBucket, "g14.web", []byte(`{"node":"g14","module":"web","build":"b-9"}`))
b.start(t) b.start(t)
wkey, _ := delivery.WantKey("ace", "web") wkey, _ := delivery.WantKey("ace", "web")
@@ -316,12 +318,25 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
if err != nil || want.FP != fNew { if err != nil || want.FP != fNew {
t.Fatalf("the canary ace's want = %+v, %v", want, err) t.Fatalf("the canary ace's want = %+v, %v", want, err)
} }
time.Sleep(500 * time.Millisecond) if w := b.wantNow(t, "shanks", "web", "shanks' first want past the canary rule", func(w delivery.Want) bool { return w.FP == fNew }); w.Composed != "sha256:"+strings.Repeat("c", 64) {
t.Errorf("shanks' want = %+v, want F naming its composition", w)
}
var neither asks.Condition
for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && neither.Key == ""; time.Sleep(100 * time.Millisecond) {
for k, c := range b.conditionsSaid(t) {
if strings.Contains(k, ".neither.") && c.State == asks.StateRaised {
neither = c
}
}
}
if neither.Subject.Machine != "g14" || neither.Subject.ID != "web" || neither.Severity != asks.SeverityUrgent || !strings.Contains(neither.Explanation, "canary") {
t.Errorf("the condition of web on neither list on g14 = %+v", neither)
}
if keys, err := b.buckets[delivery.AssignerDifferencesBucket].Keys(b.ctx); err == nil && len(keys) != 0 { if keys, err := b.buckets[delivery.AssignerDifferencesBucket].Keys(b.ctx); err == nil && len(keys) != 0 {
t.Errorf("differences were written: %v", keys) t.Errorf("differences were written: %v", keys)
} }
if e, err := b.buckets[delivery.AssignerWantsBucket].Get(b.ctx, "want.g14.web"); err == nil { if e, err := b.buckets[delivery.AssignerWantsBucket].Get(b.ctx, "want.g14.web"); err == nil {
t.Errorf("g14, held back by the canary with no want before, was given one: %s", e.Value()) t.Errorf("g14, which does not run web and is held back by the canary, was given a want: %s", e.Value())
} }
} }
@@ -415,7 +430,7 @@ func TestAFailedDataModuleIsAskedToRestore(t *testing.T) {
key := "" key := ""
for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && key == ""; time.Sleep(100 * time.Millisecond) { for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && key == ""; time.Sleep(100 * time.Millisecond) {
for k, c := range b.conditionsSaid(t) { for k, c := range b.conditionsSaid(t) {
if c.State == asks.StateRaised && c.Subject.ID == "mail" { if c.State == asks.StateRaised && c.Subject.ID == "mail" && strings.Contains(k, ".held.") {
if c.Subject.Machine != "ace" || !strings.Contains(c.Explanation, "connection refused") { if c.Subject.Machine != "ace" || !strings.Contains(c.Explanation, "connection refused") {
t.Fatalf("the condition = %+v, want it to name ace and the check's words", c) t.Fatalf("the condition = %+v, want it to name ace and the check's words", c)
} }
@@ -634,8 +649,8 @@ func TestAReleaseAnswerWritesTheWantAgain(t *testing.T) {
} }
for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) { for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline); time.Sleep(100 * time.Millisecond) {
cleared := false cleared := false
for _, c := range b.conditionsSaid(t) { for k, c := range b.conditionsSaid(t) {
cleared = cleared || (c.Subject.ID == "web" && c.State == asks.StateCleared) cleared = cleared || (c.Subject.ID == "web" && c.State == asks.StateCleared && strings.Contains(k, ".held."))
} }
if cleared { if cleared {
return return
@@ -805,8 +820,8 @@ func TestAHoldIsLiftedOnceEveryNodeRunsItHealthy(t *testing.T) {
if b.hasKey(delivery.AssignerWantsBucket, hkey) || b.hasKey(delivery.AssignerAsksBucket, akey) { if b.hasKey(delivery.AssignerWantsBucket, hkey) || b.hasKey(delivery.AssignerAsksBucket, akey) {
continue continue
} }
for _, c := range b.conditionsSaid(t) { for k, c := range b.conditionsSaid(t) {
if c.Subject.ID == "web" && c.State == asks.StateCleared && strings.Contains(c.Resolved, "lifted its hold by itself") { if c.Subject.ID == "web" && c.State == asks.StateCleared && strings.Contains(k, ".held.") && strings.Contains(c.Resolved, "lifted its hold by itself") {
if w := b.wantNow(t, "ace", "web", "the want at the lifted fingerprint", func(w delivery.Want) bool { return w.FP == fNew }); w.Rule != 0 { if w := b.wantNow(t, "ace", "web", "the want at the lifted fingerprint", func(w delivery.Want) bool { return w.FP == fNew }); w.Rule != 0 {
t.Errorf("the want = %+v", w) t.Errorf("the want = %+v", w)
} }