Merge pull request 'compose-candidate reads the candidate by its group, as the indexer stores it' (#199) from fix/compose-candidate-key into main
This commit was merged in pull request #199.
This commit is contained in:
@@ -575,13 +575,16 @@ func h8candidate(t *testing.T, b *itBed, fp, running string) {
|
|||||||
heads, _ := delivery.HeadsHash([]delivery.Head{head})
|
heads, _ := delivery.HeadsHash([]delivery.Head{head})
|
||||||
pulls := []delivery.CandidatePull{{Head: head, Modules: []string{"web"},
|
pulls := []delivery.CandidatePull{{Head: head, Modules: []string{"web"},
|
||||||
Fps: map[string]delivery.CandidateModule{"web": {FP2: fpIt("c"), Manifest: digest}}}}
|
Fps: map[string]delivery.CandidateModule{"web": {FP2: fpIt("c"), Manifest: digest}}}}
|
||||||
craw, err := delivery.EncodeCandidate(delivery.Candidate{Group: "novox/web#3", HeadsHash: heads, Pulls: pulls,
|
// The indexer keys the candidate by its group (a branch name, slash allowed); the checker asks by the composition key.
|
||||||
|
const group = "fix/3-web"
|
||||||
|
craw, err := delivery.EncodeCandidate(delivery.Candidate{Group: group, HeadsHash: heads, Pulls: pulls,
|
||||||
FPSet: delivery.FingerprintSet(pulls)})
|
FPSet: delivery.FingerprintSet(pulls)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
key, _ := delivery.CompositionKey("novox/web#3", heads)
|
ckey, _ := delivery.CandidatesKey(group)
|
||||||
if _, err := cands.Put(key, craw); err != nil {
|
key, _ := delivery.CompositionKey(group, heads)
|
||||||
|
if _, err := cands.Put(ckey, craw); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
before, _ := b.composed.Status()
|
before, _ := b.composed.Status()
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// **A check's candidate, composed in shadow on a call** (novox/hq ADR 0593 §7): the node seat's verb compose-candidate.
|
// **A check's candidate, composed in shadow on a call** (novox/hq ADR 0593 §7): the node seat's verb compose-candidate.
|
||||||
// Given the key of the indexer's candidate (delivery.CandidatesBucket, `<group>.<heads hash>`), the node composes itself
|
// Given the check's composition key (`<group>.<heads hash>`), it reads the group's candidate from the indexer
|
||||||
|
// (delivery.CandidatesBucket, keyed by the group alone, delivery.CandidatesKey) at those heads, and composes itself
|
||||||
// twice from what its shadow holds (placement, records, the indexer's modules, its config values), with its own sealed
|
// twice from what its shadow holds (placement, records, the indexer's modules, its config values), with its own sealed
|
||||||
// values: once as it is, and once with the candidate's modules laid over the indexer's (each at the candidate's
|
// values: once as it is, and once with the candidate's modules laid over the indexer's (each at the candidate's
|
||||||
// fingerprint, its manifest by digest and the indexer's verdict on it, ADR 0578 §3; a module the candidate removes left
|
// fingerprint, its manifest by digest and the indexer's verdict on it, ADR 0578 §3; a module the candidate removes left
|
||||||
@@ -47,7 +48,14 @@ func (s *Shadow) ComposeCandidate(ctx context.Context, ask delivery.ComposeCandi
|
|||||||
a.Verdict = delivery.ComposeCandidateUnready
|
a.Verdict = delivery.ComposeCandidateUnready
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
if _, _, err := delivery.ParseCompositionKey(ask.Candidate); err != nil {
|
group, heads, err := delivery.ParseCompositionKey(ask.Candidate)
|
||||||
|
if err != nil {
|
||||||
|
return refuse(err.Error())
|
||||||
|
}
|
||||||
|
// The ask names the check (the composition key, `<group>.<heads hash>`); the indexer keys the group's candidate by
|
||||||
|
// the group alone (delivery.CandidatesKey) and holds its heads hash in the value.
|
||||||
|
ckey, err := delivery.CandidatesKey(group)
|
||||||
|
if err != nil {
|
||||||
return refuse(err.Error())
|
return refuse(err.Error())
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithTimeout(ctx, delivery.ComposeAnswerBound)
|
ctx, cancel := context.WithTimeout(ctx, delivery.ComposeAnswerBound)
|
||||||
@@ -56,7 +64,7 @@ func (s *Shadow) ComposeCandidate(ctx context.Context, ask delivery.ComposeCandi
|
|||||||
return unready(errNotReady.Error())
|
return unready(errNotReady.Error())
|
||||||
}
|
}
|
||||||
base := c.scratch()
|
base := c.scratch()
|
||||||
raw, err := base.get(ctx, delivery.CandidatesBucket, ask.Candidate)
|
raw, err := base.get(ctx, delivery.CandidatesBucket, ckey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return unready(fmt.Sprintf("the indexer's candidate %s cannot be read: %v", ask.Candidate, err))
|
return unready(fmt.Sprintf("the indexer's candidate %s cannot be read: %v", ask.Candidate, err))
|
||||||
}
|
}
|
||||||
@@ -67,6 +75,11 @@ func (s *Shadow) ComposeCandidate(ctx context.Context, ask delivery.ComposeCandi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return refuse(fmt.Sprintf("the indexer's candidate %s cannot be read: %v", ask.Candidate, err))
|
return refuse(fmt.Sprintf("the indexer's candidate %s cannot be read: %v", ask.Candidate, err))
|
||||||
}
|
}
|
||||||
|
if cand.HeadsHash != heads {
|
||||||
|
// The group moved to other heads: the check asked is replaced, and this node composes not a candidate it was
|
||||||
|
// not asked about. No answer (ADR 0593 §9), never a verdict on the wrong heads.
|
||||||
|
return unready(fmt.Sprintf("the indexer's candidate of the group %s is at heads %s, not %s: the check asked is replaced", group, cand.HeadsHash, heads))
|
||||||
|
}
|
||||||
laid := c.scratch()
|
laid := c.scratch()
|
||||||
touched, err := laid.layCandidate(ctx, cand)
|
touched, err := laid.layCandidate(ctx, cand)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -29,21 +29,24 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) {
|
|||||||
key, _ := delivery.ManifestsKey(d)
|
key, _ := delivery.ManifestsKey(d)
|
||||||
b.kv[delivery.ManifestsBucket+"/"+key] = man
|
b.kv[delivery.ManifestsBucket+"/"+key] = man
|
||||||
}
|
}
|
||||||
candidateOf := func(manifest string) string {
|
// The indexer keys a candidate by its group alone (delivery.CandidatesKey, the branch name escaped, a slash
|
||||||
|
// allowed), its heads hash inside the value; the checker asks by the composition key, `<group>.<heads hash>`.
|
||||||
|
candidateOf := func(group, manifest string) string {
|
||||||
head := delivery.Head{Repository: "novox/x", Number: 7, Head: "c0ffee" + manifest[len(manifest)-6:]}
|
head := delivery.Head{Repository: "novox/x", Number: 7, Head: "c0ffee" + manifest[len(manifest)-6:]}
|
||||||
heads, _ := delivery.HeadsHash([]delivery.Head{head})
|
heads, _ := delivery.HeadsHash([]delivery.Head{head})
|
||||||
pulls := []delivery.CandidatePull{{Head: head, Modules: []string{"lemurs"},
|
pulls := []delivery.CandidatePull{{Head: head, Modules: []string{"lemurs"},
|
||||||
Fps: map[string]delivery.CandidateModule{"lemurs": {FP2: fpNew, Manifest: manifest}}}}
|
Fps: map[string]delivery.CandidateModule{"lemurs": {FP2: fpNew, Manifest: manifest}}}}
|
||||||
raw, err := delivery.EncodeCandidate(delivery.Candidate{Group: "novox/x#7", HeadsHash: heads, Pulls: pulls,
|
raw, err := delivery.EncodeCandidate(delivery.Candidate{Group: group, HeadsHash: heads, Pulls: pulls,
|
||||||
FPSet: delivery.FingerprintSet(pulls)})
|
FPSet: delivery.FingerprintSet(pulls)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
key, _ := delivery.CompositionKey("novox/x#7", heads)
|
ckey, _ := delivery.CandidatesKey(group)
|
||||||
b.kv[delivery.CandidatesBucket+"/"+key] = raw
|
b.kv[delivery.CandidatesBucket+"/"+ckey] = raw
|
||||||
|
key, _ := delivery.CompositionKey(group, heads)
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
accepted, refusedKey := candidateOf(digest2), ""
|
accepted, refusedKey := candidateOf("fix/940-no-retire-on-partial-read", digest2), ""
|
||||||
{
|
{
|
||||||
// The same change, its manifest refused by the indexer's judge.
|
// The same change, its manifest refused by the indexer's judge.
|
||||||
refusedMan := strings.Replace(lemurs2, `"version":"2"`, `"version":"3"`, 1)
|
refusedMan := strings.Replace(lemurs2, `"version":"2"`, `"version":"3"`, 1)
|
||||||
@@ -58,7 +61,7 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
b.kv[delivery.VerdictsBucket+"/"+vkey] = v
|
b.kv[delivery.VerdictsBucket+"/"+vkey] = v
|
||||||
refusedKey = candidateOf(d3)
|
refusedKey = candidateOf("fix/941-refused", d3)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := b.env()
|
env := b.env()
|
||||||
@@ -114,6 +117,13 @@ func TestTheNodeComposesACandidateOnAsk(t *testing.T) {
|
|||||||
if m := s.ComposeCandidate(ctx, delivery.ComposeCandidateAsk{Candidate: missing}); !strings.Contains(m.Refused, "holds no candidate") {
|
if m := s.ComposeCandidate(ctx, delivery.ComposeCandidateAsk{Candidate: missing}); !strings.Contains(m.Refused, "holds no candidate") {
|
||||||
t.Errorf("a candidate the indexer does not hold answered %+v", m)
|
t.Errorf("a candidate the indexer does not hold answered %+v", m)
|
||||||
}
|
}
|
||||||
|
// The group's candidate moved to other heads since the ask: the node composes not the one asked, and answers
|
||||||
|
// unready (no answer), naming the heads it holds.
|
||||||
|
moved, _ := delivery.CompositionKey("fix/940-no-retire-on-partial-read", "0123456789ab")
|
||||||
|
if m := s.ComposeCandidate(ctx, delivery.ComposeCandidateAsk{Candidate: moved}); m.Verdict != delivery.ComposeCandidateUnready ||
|
||||||
|
!strings.Contains(m.Refused, "heads") {
|
||||||
|
t.Errorf("a candidate at other heads answered %+v", m)
|
||||||
|
}
|
||||||
ended, end := context.WithCancel(ctx)
|
ended, end := context.WithCancel(ctx)
|
||||||
end()
|
end()
|
||||||
if e := s.ComposeCandidate(ended, delivery.ComposeCandidateAsk{Candidate: accepted}); e.Verdict != delivery.ComposeCandidateUnready {
|
if e := s.ComposeCandidate(ended, delivery.ComposeCandidateAsk{Candidate: accepted}); e.Verdict != delivery.ComposeCandidateUnready {
|
||||||
|
|||||||
Reference in New Issue
Block a user