A source revision is delivered over the nodes present: the view leaves out a node the watchman says is away and names it, and the node counts once it runs its wants (ADR 0603 §7)
This commit is contained in:
@@ -202,6 +202,7 @@ func (m *Model) moduleReach(name string, mod delivery.Module, heads []headAt, wa
|
||||
mr.Changes = append(mr.Changes, c)
|
||||
}
|
||||
var waiting []string
|
||||
away := m.awayNodes()
|
||||
floor, floorNode, unset := -1, "", ""
|
||||
for _, key := range wantKeys {
|
||||
node, _ := splitKey(key)
|
||||
@@ -209,6 +210,11 @@ func (m *Model) moduleReach(name string, mod delivery.Module, heads []headAt, wa
|
||||
if w.err != "" || w.value.Removed || w.value.FP == "" {
|
||||
continue
|
||||
}
|
||||
if away[node] {
|
||||
// A node away holds no head back (ADR 0603 §7); it catches up by its wants and counts once it runs them.
|
||||
waiting = append(waiting, fmt.Sprintf("%s on %s is away", name, node))
|
||||
continue
|
||||
}
|
||||
mr.Nodes++
|
||||
h, ok := m.haves[key]
|
||||
switch {
|
||||
|
||||
@@ -68,6 +68,11 @@ func TestTheViewDerivesHowFarEachRepositoryReached(t *testing.T) {
|
||||
{name: "every node runs the newest build", reached: commitC},
|
||||
{name: "a node lagging stops the head before the change it lacks", reached: commitB, holds: "web on g14",
|
||||
then: []Entry{haveFP(t, "g14", "web", fpOld, delivery.HealthOK)}, waiting: "not yet the"},
|
||||
// ADR 0603 §7: a node away holds no head back, is named, and holds it again once it returns still behind.
|
||||
{name: "a node away lagging holds no head back and is named", reached: commitC, waiting: "g14 is away",
|
||||
then: []Entry{haveFP(t, "g14", "web", fpOld, delivery.HealthOK), presenceE(t, "g14", false, t0)}},
|
||||
{name: "a node returned lagging holds the head again", reached: commitB, holds: "web on g14", waiting: "not yet the",
|
||||
then: []Entry{haveFP(t, "g14", "web", fpOld, delivery.HealthOK), presenceE(t, "g14", false, t0), presenceE(t, "g14", true, t0)}},
|
||||
{name: "a want held back at an older build holds the head as a lag does", reached: commitB, holds: "web on g14 runs the build of",
|
||||
then: []Entry{wantE(t, "g14", "web", delivery.Want{FP: fpOld, Newest: fpNew, Rule: 2}), haveFP(t, "g14", "web", fpOld, delivery.HealthOK)}},
|
||||
{name: "a failing health check is said and holds nothing", reached: commitC, waiting: "health check failed",
|
||||
|
||||
@@ -124,6 +124,9 @@ type SourceRow struct {
|
||||
Waiting []string `json:"waiting,omitempty"`
|
||||
// Wants is how many wants of modules at the indexer were looked at; a revision with none is delivered of nothing.
|
||||
Wants int `json:"wants"`
|
||||
// Away are the nodes the watchman says are offline, left out of the comparison (ADR 0603 §7): the revision counts for them
|
||||
// once they return and run their wants.
|
||||
Away []string `json:"away,omitempty"`
|
||||
}
|
||||
|
||||
// AskRow is an open ask (novox/hq ADR 0437 §10): the gate's ask with no answer under its key in the teller's answers.
|
||||
@@ -1061,7 +1064,8 @@ func (m *Model) wantRows() []WantRow {
|
||||
|
||||
// sourceRow derives the state of the source revision the gate holds (ADR 0429 §1; recorded by no part): delivered when every want
|
||||
// equals its module's fingerprint at the indexer and every have equals its want, running and healthy, and every node a module is
|
||||
// assigned to has a want. It is nil when the gate holds no readable source.
|
||||
// assigned to has a want, compared only on the nodes present: a node the watchman says is offline is named away and left out
|
||||
// (ADR 0603 §7, changing ADR 0429 §1). It is nil when the gate holds no readable source.
|
||||
func (m *Model) sourceRow() *SourceRow {
|
||||
if m.source == nil || m.source.err != "" {
|
||||
return nil
|
||||
@@ -1075,8 +1079,12 @@ func (m *Model) sourceRow() *SourceRow {
|
||||
return fp
|
||||
}
|
||||
looked := 0
|
||||
away := m.awayNodes()
|
||||
for key, w := range m.wants {
|
||||
node, module := splitKey(key)
|
||||
if away[node] {
|
||||
continue
|
||||
}
|
||||
mod, ok := m.modules[module]
|
||||
if w.err != "" || w.value.Removed || !ok || mod.err != "" || mod.value.FP2 == "" {
|
||||
continue
|
||||
@@ -1096,7 +1104,7 @@ func (m *Model) sourceRow() *SourceRow {
|
||||
}
|
||||
for _, a := range m.placedPairs() {
|
||||
wk, werr := delivery.HaveKey(a.Node, a.Module)
|
||||
if werr != nil {
|
||||
if werr != nil || away[a.Node] {
|
||||
continue
|
||||
}
|
||||
if _, ok := m.wants[wk]; ok {
|
||||
@@ -1107,13 +1115,28 @@ func (m *Model) sourceRow() *SourceRow {
|
||||
}
|
||||
}
|
||||
sort.Strings(waiting)
|
||||
row := &SourceRow{Revision: m.source.revision, State: SourceDelivered, Wants: looked}
|
||||
row := &SourceRow{Revision: m.source.revision, State: SourceDelivered, Wants: looked, Away: slices.Sorted(maps.Keys(away))}
|
||||
if len(waiting) > 0 {
|
||||
row.State, row.Waiting = SourceNotDelivered, waiting
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
// awayNodes are the nodes whose presence the watchman says is offline (a node's own key, not a part instance's): a source
|
||||
// revision is delivered over the nodes present (ADR 0603 §7). Called with the lock held.
|
||||
func (m *Model) awayNodes() map[string]bool {
|
||||
out := map[string]bool{}
|
||||
for key, p := range m.presence {
|
||||
if p.err != "" || p.value.Online {
|
||||
continue
|
||||
}
|
||||
if node, part, _, err := delivery.ParsePresenceKey(key); err == nil && part == "" {
|
||||
out[node] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// splitKey is the node and the module (unescaped, issue 1150) of a model key, which is the have bucket's key form.
|
||||
func splitKey(key string) (node, module string) {
|
||||
node, module, isNode, err := delivery.ParseHaveKey(key)
|
||||
|
||||
@@ -124,6 +124,16 @@ func TestASourceRevisionIsDerivedFromWantsAndHaves(t *testing.T) {
|
||||
if s.State != SourceNotDelivered || !strings.Contains(strings.Join(s.Waiting, ";"), "novox") {
|
||||
t.Errorf("a node assigned the module with no want: %+v", s)
|
||||
}
|
||||
// ADR 0603 §7: a node away holds nothing back and is named; it counts once it returns and runs its wants.
|
||||
fill(m, presenceE(t, "novox", false, t0))
|
||||
s = m.Snapshot().Source
|
||||
if s.State != SourceDelivered || len(s.Away) != 1 || s.Away[0] != "novox" {
|
||||
t.Errorf("a node away: %+v", s)
|
||||
}
|
||||
fill(m, presenceE(t, "novox", true, t0))
|
||||
if s = m.Snapshot().Source; s.State != SourceNotDelivered || len(s.Away) != 0 {
|
||||
t.Errorf("the node returned and runs nothing yet: %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSourceRevisionHeldMeansNoStateIsDerived(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user