Delete the builder, the build asks, the build queue and the merge gate: the builder's keys are the only way a module is built (issues 1851, 1299, 1318; C2b-C5, C10)
The controller asks no build of anybody (ADR 0501 §5, ADR 0525 clause 6, ADR 0503 §3). Every module of a walk is taken from the builder's done key; a key not done yet waits, named in the plan, and never fails it. - C2b/C2c: internal/builder and cmd/mesh-controller's asks go (askABuild, onlyTheBuildersOwn, buildOneAsked, takeIn from an outcome, buildAgentsOwn, the builder's say and its fallback, the daemon hearing an outcome). The server follows the retired seats' built subjects only to let go of them, because the installer's carried user list names them. - C2d: replay, queue, cancel, clear, kill, pause, resume, delivery-check, check-here and merge-gate are refused by name. - C3: the check queues (CHECKS_READY, CHECKS_OTHER) and their workers, the checks-running and plans-building buckets and their grants, the build queue and its hold (plans_building.go, queue.go, link/holds.go, builds_nats.go, checks_nats.go). - C4: merge_gate*.go, the pull request's repo-check path, the Checked event and its layers; a pull request asks the controller nothing but to tell mesh-config what its head declares. - C5: the builds bucket is written only by key_takein. - C10 (help and refusals): the terminal's help lists only what is served. Integration tests of the plan's tiers, sends, stops and retries now run on a stand-in builder with a done key for every module (walk_keys_scaffold_integration_test.go); the tests of the asks, the queue, the merge gate and the replays are deleted.
This commit is contained in:
@@ -9,7 +9,6 @@ package builderkeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -310,99 +309,3 @@ func firstLine(s string) string {
|
||||
line, _, _ := strings.Cut(strings.TrimSpace(s), "\n")
|
||||
return line
|
||||
}
|
||||
|
||||
// The builder's say of what its keys hold (novox/hq issue 1886): each running builder instance keeps a key, named by its
|
||||
// node, in the builder's own announced bucket of instances, saying what the keys it writes hold. An instance that writes
|
||||
// references (ADR 0498 §1) says WritesReferences there; a builder from before R4e keeps no such key, and has no bucket.
|
||||
// The bucket is the builder's and the shape is read by the controller alone, so neither is in mesh-sdk.
|
||||
const (
|
||||
// BuilderInstancesBucket is the builder's bucket of its running instances, `<owner module>_<name>`.
|
||||
BuilderInstancesBucket = delivery.BuilderModule + "_instances"
|
||||
// WritesReferences is the word an instance says when every done key it writes names where its artifacts are.
|
||||
WritesReferences = "references"
|
||||
)
|
||||
|
||||
// instanceSays is an instance's value in BuilderInstancesBucket: what the keys it writes hold.
|
||||
type instanceSays struct {
|
||||
Writes []string `json:"writes"`
|
||||
}
|
||||
|
||||
// saysReferences says whether any instance's value says it writes references. A value that does not decode says
|
||||
// nothing.
|
||||
func saysReferences(values [][]byte) bool {
|
||||
for _, v := range values {
|
||||
var says instanceSays
|
||||
if json.Unmarshal(v, &says) != nil {
|
||||
continue
|
||||
}
|
||||
for _, w := range says.Writes {
|
||||
if w == WritesReferences {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SaysReferences says whether a running builder instance says, in its bucket of instances, that it writes references. No
|
||||
// bucket (the builder from before R4e, which never made it) and no key say no; a bucket that cannot be read is an error.
|
||||
func (r Reader) SaysReferences(ctx context.Context) (bool, error) {
|
||||
kv, err := r.bucket(ctx, BuilderInstancesBucket)
|
||||
if err != nil || kv == nil {
|
||||
return false, err
|
||||
}
|
||||
keys, err := kv.Keys(ctx)
|
||||
if errors.Is(err, jetstream.ErrNoKeysFound) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("listing the builder's instances: %w", err)
|
||||
}
|
||||
var values [][]byte
|
||||
for _, key := range keys {
|
||||
e, err := kv.Get(ctx, key)
|
||||
if errors.Is(err, jetstream.ErrKeyNotFound) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the builder's instance %s: %w", key, err)
|
||||
}
|
||||
values = append(values, e.Value())
|
||||
}
|
||||
return saysReferences(values), nil
|
||||
}
|
||||
|
||||
// WatchSays calls wake for every change of the builder's bucket of instances from now on, until ctx ends: what makes the
|
||||
// look judge an open walk again when the builder starts or stops saying it writes references (issue 1886), with no
|
||||
// timer. It says false, and watches nothing, while the bucket is not there; the caller asks again when the builder
|
||||
// writes a key, which a builder that makes the bucket does.
|
||||
func WatchSays(ctx context.Context, js jetstream.JetStream, wake func()) (bool, error) {
|
||||
kv, err := js.KeyValue(ctx, BuilderInstancesBucket)
|
||||
if errors.Is(err, jetstream.ErrBucketNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the bucket %s: %w", BuilderInstancesBucket, err)
|
||||
}
|
||||
w, err := kv.WatchAll(ctx, jetstream.UpdatesOnly())
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("watching %s: %w", BuilderInstancesBucket, err)
|
||||
}
|
||||
go func() {
|
||||
defer w.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case e, ok := <-w.Updates():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if e != nil {
|
||||
wake() // a put and a delete both change what the builder says
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
package builderkeys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
)
|
||||
|
||||
// 1886/AC1 and AC4 on a real bus: no bucket of instances says no and watches nothing; an instance's key saying
|
||||
// references says yes, and its put and its delete each wake the watcher, with no timer.
|
||||
func TestTheBuildersSayIsReadAndWatchedOnTheBus(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
js := aBus(t)
|
||||
r := Reader{JS: js}
|
||||
if says, err := r.SaysReferences(ctx); err != nil || says {
|
||||
t.Fatalf("no bucket: %v %v", says, err)
|
||||
}
|
||||
if on, err := WatchSays(ctx, js, func() {}); err != nil || on {
|
||||
t.Fatalf("no bucket is watched: %v %v", on, err)
|
||||
}
|
||||
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: BuilderInstancesBucket, History: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
woken := make(chan struct{}, 4)
|
||||
if on, err := WatchSays(ctx, js, func() { woken <- struct{}{} }); err != nil || !on {
|
||||
t.Fatalf("the bucket is not watched: %v %v", on, err)
|
||||
}
|
||||
wake := func(what string) {
|
||||
t.Helper()
|
||||
select {
|
||||
case <-woken:
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("%s woke nothing", what)
|
||||
}
|
||||
}
|
||||
put(t, kv, "shanks", []byte(`{"writes":["references"]}`))
|
||||
wake("a put")
|
||||
if says, err := r.SaysReferences(ctx); err != nil || !says {
|
||||
t.Fatalf("an instance saying references: %v %v", says, err)
|
||||
}
|
||||
if err := kv.Delete(ctx, "shanks"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wake("a delete")
|
||||
if says, err := r.SaysReferences(ctx); err != nil || says {
|
||||
t.Fatalf("no instance left: %v %v", says, err)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package builderkeys
|
||||
|
||||
import "testing"
|
||||
|
||||
// 1886/AC1: a builder that keeps no say, or a say without the word, is a builder that does not write references; one
|
||||
// instance that says it is enough, since it builds every key done without a reference again (ADR 0498 §8).
|
||||
func TestTheBuilderSaysReferencesOnlyWhenAnInstanceSaysTheWord(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
values [][]byte
|
||||
want bool
|
||||
}{
|
||||
{"no instance", nil, false},
|
||||
{"an instance saying nothing", [][]byte{[]byte(`{"writes":[]}`)}, false},
|
||||
{"a value not read", [][]byte{[]byte(`{not json`)}, false},
|
||||
{"another word", [][]byte{[]byte(`{"writes":["digests"]}`)}, false},
|
||||
{"one instance says it", [][]byte{[]byte(`{"writes":["digests"]}`), []byte(`{"v":1,"writes":["references"]}`)}, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := saysReferences(c.values); got != c.want {
|
||||
t.Errorf("%s: says references %v, want %v", c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user