mesh/delivery closed: a newer head of the same pull request
mesh/check failed: unit: git.novox.be/novox/mesh-catalog/checks/nobinaries.TestTheCatalogueHoldsNoCompiledProgram: modules/mesh-config/cmd/mesh-config…
One commit for the branch feat/1126-nodes-compose of mesh-delivery: its 16 commits squashed so its history does not carry the 17.8 MB compiled mesh-delivery-assigner a commit added by mistake and a later one removed (issue 1577). The tree equals the former head ff6c038d. The operator allowed this rewrite of the feature branch on 2026-10-26.
124 lines
4.4 KiB
Go
124 lines
4.4 KiB
Go
package index
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"git.novox.be/novox/mesh-sdk/go/process"
|
|
)
|
|
|
|
// Placement of modules on nodes (novox/hq ADR 0549 §2, issue 1126): at each merge of the catalogue the forge bridge
|
|
// carries every placement file of the merge commit, and the indexer writes each node's key `placement.<node>` in its
|
|
// state `placement` only when it differs (the revision alone is no change), deletes the key of a node whose file is
|
|
// gone, and says `placement-changed` for each node it wrote or deleted, naming the modules added, removed or changed.
|
|
// A merge that changes no placement writes nothing and says nothing.
|
|
//
|
|
// A file that does not read as a placement, or names another node than its file, is said in the log and its key is left
|
|
// as it was: the catalogue's own check refuses such a file before it merges (ADR 0549 §1), so it is a fault of that
|
|
// check, and a node keeps composing from the placement it had rather than from none.
|
|
|
|
// applyPlacement takes the placement files of a merged message in; a message that read none (another repository, an
|
|
// older bridge) changes nothing.
|
|
func (ix *Indexer) applyPlacement(ctx context.Context, f delivery.Forge) error {
|
|
if ix.Placement == nil || f.Kind != delivery.ForgeMerged || !f.PlacementsRead {
|
|
return nil
|
|
}
|
|
keys, err := ix.Placement.Keys(ctx)
|
|
if errors.Is(err, process.ErrNotYet) {
|
|
// The state is not on the bus yet: said, and the merge's candidates are still taken out (issue 1790's binding
|
|
// writes the next catalogue merge's placement once it appears).
|
|
ix.logf("%s at %s: its placement files are not written: %v", f.Repository, f.Merge, err)
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("reading the placement keys: %w", err)
|
|
}
|
|
gone := map[string]bool{}
|
|
for _, key := range keys {
|
|
if node, err := delivery.ParsePlacementKey(key); err == nil {
|
|
gone[node] = true
|
|
}
|
|
}
|
|
var refused []string
|
|
for _, node := range slices.Sorted(maps.Keys(f.Placements)) {
|
|
delete(gone, node)
|
|
next, err := delivery.DecodePlacement([]byte(f.Placements[node]))
|
|
if err == nil && next.Node != node {
|
|
err = fmt.Errorf("the file of %s names the node %q", node, next.Node)
|
|
}
|
|
if err != nil {
|
|
refused = append(refused, node)
|
|
ix.logf("%s at %s: the placement file of %s is refused and placement.%s is left as it was (the catalogue's check should have refused it): %v",
|
|
f.Repository, f.Merge, node, node, err)
|
|
continue
|
|
}
|
|
next.Revision = f.Merge
|
|
if err := ix.writePlacement(ctx, node, &next); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, node := range slices.Sorted(maps.Keys(gone)) {
|
|
if err := ix.writePlacement(ctx, node, nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(refused) > 0 {
|
|
// Said, and the merge's candidates are still taken out: a refused file holds nobody's delivery.
|
|
ix.logf("%s at %s: the placement files of %v do not read; their keys are left as they were", f.Repository, f.Merge, refused)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// writePlacement writes one node's key, or deletes it for next nil, only when it differs, and says what changed.
|
|
func (ix *Indexer) writePlacement(ctx context.Context, node string, next *delivery.Placement) error {
|
|
key, err := delivery.PlacementKey(node)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var change *delivery.PlacementChange
|
|
err = write(ctx, ix.Placement, key, func(value []byte, found bool) ([]byte, bool, error) {
|
|
change = nil
|
|
var before delivery.Placement
|
|
if found {
|
|
cur, err := delivery.DecodePlacement(value)
|
|
if err != nil {
|
|
ix.logf("placement.%s holds what is not a placement, written again: %v", node, err)
|
|
} else {
|
|
before = cur
|
|
}
|
|
}
|
|
if next == nil {
|
|
if !found {
|
|
return nil, false, nil
|
|
}
|
|
c := delivery.ChangeOf(before, delivery.Placement{Node: node})
|
|
change = &c
|
|
return nil, true, nil
|
|
}
|
|
if found && delivery.SamePlacement(before, *next) {
|
|
return nil, false, nil
|
|
}
|
|
data, err := delivery.EncodePlacement(*next)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
c := delivery.ChangeOf(before, *next)
|
|
change = &c
|
|
return data, false, nil
|
|
})
|
|
if err != nil || change == nil {
|
|
return err
|
|
}
|
|
if ix.Say != nil {
|
|
if err := ix.Say(delivery.PlacementChangedEvent, *change); err != nil {
|
|
// The key is written: a reader that watches it hears the change; the event is said loudly as not said.
|
|
ix.logf("placement.%s is written, and %s could not be said: %v", node, delivery.PlacementChangedEvent, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|