Assigner: lay each held value of the group's own pull requests at their heads over its module's values in the composition check, judged against the head's declaration and named, so a check reads the held values itself (issue 1953 D7, ADR 0565 §5)
This commit is contained in:
@@ -73,6 +73,7 @@ var readBuckets = []string{
|
||||
delivery.StepsBucket(goBuildModule),
|
||||
takeoverBucket,
|
||||
provisions.ProvisionsBucket,
|
||||
keptBucket,
|
||||
}
|
||||
|
||||
// takeoverBucket is the condition keeper's take-over key's bucket (novox/hq ADR 0564 §9): whether the assigner's said
|
||||
@@ -159,6 +160,11 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
|
||||
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
|
||||
continue
|
||||
}
|
||||
if name == keptBucket && err != nil {
|
||||
// mesh-config's held values (ADR 0565 §5): without them a composition lays none, said in the journal.
|
||||
logf("the bucket %s cannot be opened (%v): no held value is laid on a composition until it is bound", name, err)
|
||||
continue
|
||||
}
|
||||
if name == provisions.ProvisionsBucket && err != nil {
|
||||
// The seat mesh-provisioner's state, with its own delivery (ADR 0570 §4): bound once the bus says it was made;
|
||||
// until then no want waits for a provision, as before.
|
||||
@@ -322,6 +328,7 @@ var watched = []string{
|
||||
delivery.AssignerWantsBucket,
|
||||
assign.PoliciesBucket,
|
||||
provisions.ProvisionsBucket,
|
||||
keptBucket,
|
||||
}
|
||||
|
||||
// follow watches every bucket a pass reads and makes a pass at start, once every watch handed what stands, and after
|
||||
@@ -418,7 +425,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
}
|
||||
started := 0
|
||||
for _, name := range watched {
|
||||
if kvs[name] == nil && (slices.Contains(seatBuckets, name) || sourceOfGrants(name) || name == provider.AssignmentsBucket || name == assign.PoliciesBucket || name == provisions.ProvisionsBucket) {
|
||||
if kvs[name] == nil && (slices.Contains(seatBuckets, name) || sourceOfGrants(name) || name == provider.AssignmentsBucket || name == assign.PoliciesBucket || name == provisions.ProvisionsBucket || name == keptBucket) {
|
||||
continue // not on the bus at this start (open said so)
|
||||
}
|
||||
started++
|
||||
@@ -448,7 +455,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
}
|
||||
}
|
||||
// And the two that choose where grants are read from, with the same delivery as the grants part that writes one.
|
||||
for _, name := range []string{delivery.PartGrantsBucket, buscompose.LeaseBucket, assign.PoliciesBucket, provisions.ProvisionsBucket} {
|
||||
for _, name := range []string{delivery.PartGrantsBucket, buscompose.LeaseBucket, assign.PoliciesBucket, provisions.ProvisionsBucket, keptBucket} {
|
||||
if kvs[name] == nil && js != nil {
|
||||
name := name
|
||||
process.AwaitBucket(ctx, js, nc, name, "until then the bucket is not read",
|
||||
|
||||
@@ -432,6 +432,11 @@ func (r worldReader) Served(ctx context.Context, node, module string, keys []str
|
||||
func (w *world) change(ctx context.Context, manifests getter, c delivery.Candidate, trunk map[string]delivery.Module) (assign.Change, string) {
|
||||
ch := assign.Change{Group: c.Group, Heads: c.HeadsHash, Manifests: map[string]catalogue.Manifest{}, Unreadable: map[string]string{},
|
||||
Twice: map[string][]string{}, Digests: map[string]string{}, Members: map[string]string{}}
|
||||
heads := map[string]string{}
|
||||
for _, p := range c.Pulls {
|
||||
heads[fmt.Sprintf("%s#%d", p.Repository, p.Number)] = p.Head.Head
|
||||
}
|
||||
ch.Held = assign.HeldOf(w.read(keptBucket), heads)
|
||||
definedIn := map[string]map[string]bool{}
|
||||
for _, p := range c.Pulls {
|
||||
if p.Repository == assign.SDKRepository {
|
||||
@@ -675,3 +680,6 @@ func (w *world) runningCommits(ctx context.Context, get getter, module string) [
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// keptBucket is mesh-config's `kept`: the approved held values of open pull requests (ADR 0565 §5).
|
||||
var keptBucket = "mesh-config_" + delivery.MeshConfigKeptState
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package assign
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -59,6 +60,63 @@ type Change struct {
|
||||
// Members the head of each member by repository: what a composer at the group's mesh-sdk reads (issues 1622, 1623).
|
||||
Digests map[string]string
|
||||
Members map[string]string
|
||||
// Held are the approved held values of the group's pull requests at the heads judged (mesh-config's `kept`, novox/hq ADR
|
||||
// 0565 §5, ADR 0502 §4): each laid over its module's values on its node in the change's composition, and named.
|
||||
Held []delivery.KeptValue
|
||||
}
|
||||
|
||||
// HeldOf are the held values kept for the pull requests the candidate names, each at the head it names: one of another
|
||||
// pull request, or at another head, is never laid (ADR 0502 §4). kept is mesh-config's `kept` by key.
|
||||
func HeldOf(kept map[string][]byte, heads map[string]string) []delivery.KeptValue {
|
||||
var out []delivery.KeptValue
|
||||
for _, raw := range kept {
|
||||
var k delivery.KeptValue
|
||||
if json.Unmarshal(raw, &k) != nil || k.Node == "" || k.Module == "" || k.Key == "" {
|
||||
continue
|
||||
}
|
||||
if head, ok := heads[k.Pull]; ok && head == k.Head {
|
||||
out = append(out, k)
|
||||
}
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
a, b := out[i], out[j]
|
||||
return a.Pull+a.Node+a.Module+a.Key < b.Pull+b.Node+b.Module+b.Key
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// layHeld lays each held value over its module's values on its node, as a layer of its own after the rest, and answers
|
||||
// the words that name each; judged first against the head's declaration of its key (catalogue.ValueProblems), a value the
|
||||
// declaration refuses, or a key it does not declare, is a failure.
|
||||
func layHeld(in *compose.Input, held []delivery.KeptValue) (said, fails []string) {
|
||||
for _, k := range held {
|
||||
var v any
|
||||
if err := json.Unmarshal(k.Value, &v); err != nil {
|
||||
said = append(said, fmt.Sprintf("the held value %s of %s on %s for %s cannot be read, so it is not laid: %v", k.Key, k.Module, k.Node, k.Pull, err))
|
||||
continue
|
||||
}
|
||||
d, declared := in.Catalogue[k.Module].Settings[k.Key]
|
||||
if !declared {
|
||||
fails = append(fails, fmt.Sprintf("the held value %s of %s on %s for %s: the head declares no such key", k.Key, k.Module, k.Node, k.Pull))
|
||||
continue
|
||||
}
|
||||
if problems := catalogue.ValueProblems(d, v); len(problems) > 0 {
|
||||
fails = append(fails, fmt.Sprintf("the held value %s of %s on %s for %s does not hold to the head's declaration: %s", k.Key, k.Module,
|
||||
k.Node, k.Pull, strings.Join(problems, "; ")))
|
||||
continue
|
||||
}
|
||||
at := slices.IndexFunc(in.Nodes, func(n compose.Node) bool { return n.Name == k.Node })
|
||||
if at < 0 {
|
||||
said = append(said, fmt.Sprintf("the held value %s of %s for %s names %s, which runs no module of the mesh: not laid", k.Key, k.Module, k.Pull, k.Node))
|
||||
continue
|
||||
}
|
||||
if in.Nodes[at].Settings == nil {
|
||||
in.Nodes[at].Settings = map[string][]catalogue.Layer{}
|
||||
}
|
||||
in.Nodes[at].Settings[k.Module] = append(in.Nodes[at].Settings[k.Module], catalogue.Layer{From: "held for " + k.Pull, Values: map[string]any{k.Key: v}})
|
||||
said = append(said, fmt.Sprintf("held value laid: %s of %s on %s, held for %s at %.12s, approved by %s (novox/hq ADR 0502 §4)", k.Key, k.Module, k.Node, k.Pull, k.Head, k.Approved))
|
||||
}
|
||||
return said, fails
|
||||
}
|
||||
|
||||
// Touched is the machines a change touches (ADR 0446 clause 3): those running a module the change moves, adds or
|
||||
@@ -171,6 +229,11 @@ func Compose(m Mesh, c Change, sdk string, took time.Duration) delivery.Composit
|
||||
delete(changed, name)
|
||||
}
|
||||
after := input(changed, m.Assigned)
|
||||
laid, heldFails := layHeld(&after, c.Held)
|
||||
v.Warnings = append(v.Warnings, laid...)
|
||||
for _, f := range heldFails {
|
||||
failAt("", f)
|
||||
}
|
||||
for _, node := range machines {
|
||||
_, before := compose.Resolve(trunk, node)
|
||||
_, now := compose.Resolve(after, node)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package assign
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -38,6 +40,32 @@ func TestAChangeOfOneModuleComposesItsMachine(t *testing.T) {
|
||||
if _, err := delivery.EncodeCompositionVerdict(v); err != nil {
|
||||
t.Errorf("the verdict cannot be written: %v", err)
|
||||
}
|
||||
// 1953/D7 (ADR 0565 §5, ADR 0502 §4): the group's own pull request's held value at its head is laid and named, judged
|
||||
// against the head's declaration; one of another pull request or another head is not laid.
|
||||
web, err := catalogue.ParseManifest([]byte(`{"module":"web","version":"2","config":{"port":{"type":"number","default":80,"description":"the port, a number","runtime":true}}}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kept := map[string][]byte{}
|
||||
for i, k := range []delivery.KeptValue{
|
||||
{Pull: "novox/x#1", Head: "h1", Node: "g14", Module: "web", Key: "port", Value: json.RawMessage(`81`), Approved: "op"},
|
||||
{Pull: "novox/x#2", Head: "h2", Node: "g14", Module: "web", Key: "port", Value: json.RawMessage(`82`)},
|
||||
{Pull: "novox/x#1", Head: "old", Node: "g14", Module: "web", Key: "other", Value: json.RawMessage(`1`)},
|
||||
} {
|
||||
kept[strconv.Itoa(i)], _ = json.Marshal(k)
|
||||
}
|
||||
held := HeldOf(kept, map[string]string{"novox/x#1": "h1"})
|
||||
if len(held) != 1 || string(held[0].Value) != "81" {
|
||||
t.Fatalf("held = %+v, want #1's at its head alone", held)
|
||||
}
|
||||
c = Change{Group: "g", Heads: "0123456789ab", Manifests: map[string]catalogue.Manifest{"web": web}, Held: held}
|
||||
if v := Compose(meshOf(), c, "x", 0); v.State != delivery.CompositionPass || !hasWarning(v, "held value laid: port of web on g14, held for novox/x#1") {
|
||||
t.Errorf("composition = %+v", v)
|
||||
}
|
||||
c.Held[0].Value = json.RawMessage(`"eighty"`)
|
||||
if v := Compose(meshOf(), c, "x", 0); v.State != delivery.CompositionFail || !strings.Contains(v.Words, "does not hold to the head's declaration") {
|
||||
t.Errorf("a held value the declaration refuses: %+v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// 1297/AC2: a change that removes a module a machine runs fails, naming the machine and the module.
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestManifest(t *testing.T) {
|
||||
}
|
||||
reads := slices.Clone(m.Reads)
|
||||
slices.Sort(reads)
|
||||
want := []string{"mesh-build-go.steps", "mesh-conditions.takeover", "mesh-config.resolved", "mesh-controller.assignments", "mesh-controller.builds", "mesh-controller.composed", "mesh-controller.grants", "mesh-controller.lease",
|
||||
want := []string{"mesh-build-go.steps", "mesh-conditions.takeover", "mesh-config.kept", "mesh-config.resolved", "mesh-controller.assignments", "mesh-controller.builds", "mesh-controller.composed", "mesh-controller.grants", "mesh-controller.lease",
|
||||
"mesh-delivery-builder.builds", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.manifests",
|
||||
"mesh-delivery-indexer.modules", "mesh-delivery-teller.answers", "mesh-delivery-watchman.presence", "mesh-grants.grants", "mesh-provisioner.provisions", "node-engine.have"}
|
||||
if !reflect.DeepEqual(reads, want) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"reads": [
|
||||
"mesh-build-go.steps",
|
||||
"mesh-conditions.takeover",
|
||||
"mesh-config.kept",
|
||||
"mesh-config.resolved",
|
||||
"mesh-controller.assignments",
|
||||
"mesh-controller.builds",
|
||||
|
||||
Reference in New Issue
Block a user