Keep an assignment of a module whose key is coming pending on that key and ask no build; nothing asks a request again at start (issue 1851, A10)
This commit is contained in:
@@ -51,6 +51,10 @@ func aBuilderOnTheBus(t *testing.T) builderOnTheBus {
|
||||
servingBus.Store(js)
|
||||
t.Cleanup(func() { servingBus.Store(nil) })
|
||||
withTrunkList(t, true)
|
||||
// The indexer on this bus is read, not the "not held" a mesh with no bus reads (aMeshOnTheStore).
|
||||
wasIndexed := indexedModule
|
||||
indexedModule = theIndexedModule
|
||||
t.Cleanup(func() { indexedModule = wasIndexed })
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -130,3 +134,6 @@ func asksWithPaths(t *testing.T) *[][3]string {
|
||||
t.Cleanup(func() { askABuild = was })
|
||||
return &asked
|
||||
}
|
||||
|
||||
// theIndexedModule is the look at the indexer as the controller makes it, kept before any test replaces it.
|
||||
var theIndexedModule = indexedModule
|
||||
|
||||
@@ -51,55 +51,13 @@ func attachTheChain(ctx context.Context, inv *inventory.Inventory, server *link.
|
||||
fmt.Printf("chain: the builds are not laid out under their modules yet, and none is read until they are: %v\n", err)
|
||||
go layBuildsOutSoon(ctx, inv)
|
||||
}
|
||||
// And the requests the move of delivery 1 found never handed over, asked again once (askAgain), once it is serving.
|
||||
go askAgainSoon(ctx, inv)
|
||||
// The requests the move of delivery 1 found never handed over are not asked again: the controller asks no build
|
||||
// (novox/hq ADR 0501; issue 1851, A10), and the builder builds every fingerprint the indexer writes.
|
||||
// And what delivery 1 recorded of what each machine was sent, copied onto the keys of the assignments (CopySentBuilds).
|
||||
go copySentBuildsSoon(ctx, inv)
|
||||
return nil
|
||||
}
|
||||
|
||||
// askAgain asks once more for each build request the move found never handed over (novox/hq issue 981 note 7 Q5): the
|
||||
// requests a merge could not ask for, which a person's hand `build` would otherwise have to repeat. **At most once**: a
|
||||
// request is claimed before it is asked and settled after, so a crash between loses the ask, which is where it stood.
|
||||
func askAgain(ctx context.Context, inv *inventory.Inventory) {
|
||||
pending, err := inv.RequestsToAskAgain(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("chain: the requests never handed over cannot be read: %v\n", err)
|
||||
return
|
||||
}
|
||||
for _, r := range pending {
|
||||
if claimed, err := inv.ClaimToAskAgain(ctx, r.ID); err != nil || !claimed {
|
||||
if err != nil {
|
||||
fmt.Printf("chain: %s cannot be claimed to be asked again: %v\n", r.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
id, err := askABuild(ctx, buildSource{Repository: r.Repository, Seat: r.Seat}, r.Path, r.Ref)
|
||||
if err != nil {
|
||||
if serr := inv.AskedAgainAs(ctx, r.ID, "not asked again: "+err.Error()); serr != nil {
|
||||
fmt.Printf("chain: %s was not asked again, and that could not be kept: %v\n", r.ID, serr)
|
||||
}
|
||||
fmt.Printf("chain: %s was never handed over and cannot be asked again: %v\n", r.ID, err)
|
||||
continue
|
||||
}
|
||||
recordBuildRequest(ctx, inv, inventory.BuildRequest{ID: id, Repository: r.Repository, Seat: r.Seat, Path: r.Path,
|
||||
Ref: r.Ref, Commit: r.Commit, For: r.For})
|
||||
if err := inv.AskedAgainAs(ctx, r.ID, id); err != nil {
|
||||
fmt.Printf("chain: %s was asked again as %s, and that could not be kept: %v\n", r.ID, id, err)
|
||||
}
|
||||
fmt.Printf("chain: %s, never handed over, is asked again as %s\n", r.ID, id)
|
||||
}
|
||||
}
|
||||
|
||||
// askAgainSoon waits for the controller to be serving, which an ask needs, and then asks again.
|
||||
func askAgainSoon(ctx context.Context, inv *inventory.Inventory) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(time.Minute):
|
||||
askAgain(ctx, inv)
|
||||
}
|
||||
}
|
||||
|
||||
// kindSentBuildsNotCopied is the condition of the one-time copy of node.sent_builds onto the assignments' keys that has not
|
||||
// succeeded: until it has, a module an assignment pulled in may be missing from what a machine is read to run.
|
||||
const kindSentBuildsNotCopied = "sent-builds-not-copied"
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/licences"
|
||||
@@ -58,6 +59,13 @@ func aMeshOnTheStore(t *testing.T) *stores {
|
||||
// A condition store of its own, held in memory (novox/hq to-be 45 §2): status leads with what is
|
||||
// open, and a mesh with no bus would otherwise read as one whose conditions cannot be read.
|
||||
withConditionsInMemory(t)
|
||||
// The indexer holds no module unless a test puts one on its bus (aBuilderOnTheBus): an assignment's look at it
|
||||
// reads "not held" on a mesh with no bus of its own.
|
||||
wasIndexed := indexedModule
|
||||
indexedModule = func(context.Context, string) (sdkdelivery.Module, bool, error) {
|
||||
return sdkdelivery.Module{}, false, nil
|
||||
}
|
||||
t.Cleanup(func() { indexedModule = wasIndexed })
|
||||
for _, m := range provided {
|
||||
if err := open.inventory.Provide(t.Context(), m); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -57,15 +57,21 @@ func takeNewModules(ctx context.Context, open *stores, only ...string) {
|
||||
return
|
||||
}
|
||||
defer closeKeys()
|
||||
takeNewModulesWith(ctx, open.inventory, keys, only...)
|
||||
for _, module := range takeNewModulesWith(ctx, open.inventory, keys, only...) {
|
||||
// An assignment waiting for it is made now (ADR 0501 §6; issue 1851, A10).
|
||||
for _, line := range settlePendingOnKey(ctx, open, module) {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// takeNewModulesWith is takeNewModules on the buckets handed in.
|
||||
func takeNewModulesWith(ctx context.Context, inv *inventory.Inventory, keys newModuleReads, only ...string) {
|
||||
// takeNewModulesWith is takeNewModules on the buckets handed in; it answers the modules it registered.
|
||||
func takeNewModulesWith(ctx context.Context, inv *inventory.Inventory, keys newModuleReads, only ...string) []string {
|
||||
entries, err := inv.Catalogued(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("new modules: the catalogue cannot be read: %v\n", err)
|
||||
return
|
||||
// empty-on-error: said above; nothing is registered this time, and the next write of a key looks again
|
||||
return nil
|
||||
}
|
||||
held := map[string]bool{}
|
||||
for _, e := range entries {
|
||||
@@ -74,7 +80,8 @@ func takeNewModulesWith(ctx context.Context, inv *inventory.Inventory, keys newM
|
||||
values, unread, err := keys.Modules(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("new modules: the indexer's modules cannot be read: %v\n", err)
|
||||
return
|
||||
// empty-on-error: said above; nothing is registered this time, and the next write of a key looks again
|
||||
return nil
|
||||
}
|
||||
for module, err := range unread {
|
||||
if !held[module] && (len(only) == 0 || slices.Contains(only, module)) {
|
||||
@@ -88,12 +95,14 @@ func takeNewModulesWith(ctx context.Context, inv *inventory.Inventory, keys newM
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
var registered []string
|
||||
for _, module := range names {
|
||||
v := values[module]
|
||||
_, _, err := takeInKey(ctx, inv, keys, keyTake{Module: module, Trunk: v, Ask: registrationAsk{Merge: v.Commit}})
|
||||
switch {
|
||||
case err == nil:
|
||||
forgetNewModuleWait(module)
|
||||
registered = append(registered, module)
|
||||
fmt.Printf("%s is new in %s at %.8s: registered from the builder's done key, assigned nowhere\n", module,
|
||||
v.Repository, v.Commit)
|
||||
case errors.Is(err, errKeyWaits):
|
||||
@@ -107,6 +116,7 @@ func takeNewModulesWith(ctx context.Context, inv *inventory.Inventory, keys newM
|
||||
Why: "not registered: " + err.Error()})
|
||||
}
|
||||
}
|
||||
return registered
|
||||
}
|
||||
|
||||
// newModuleReads is what the take-in of new modules reads of the builder and the indexer.
|
||||
|
||||
+116
-22
@@ -9,6 +9,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/builderkeys"
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
@@ -85,8 +88,30 @@ const (
|
||||
unknownModule = iota
|
||||
buildInFlight
|
||||
knownNotBuilt
|
||||
// keyComing is a module the indexer holds and the catalogue does not yet: registered from the builder's done key
|
||||
// of it (ADR 0501 §6), which nobody asks.
|
||||
keyComing
|
||||
)
|
||||
|
||||
// keyPendingPrefix opens the build a pending assignment waits for when it waits for a module's key: no build is asked
|
||||
// (ADR 0501 §5, §6; issue 1851, A10), so it names the module's key, not a build id.
|
||||
const keyPendingPrefix = "key:"
|
||||
|
||||
// indexedModule is the indexer's trunk value of a module the catalogue does not hold, found when it holds one. A
|
||||
// variable so a test needs no bus.
|
||||
var indexedModule = func(ctx context.Context, module string) (sdkdelivery.Module, bool, error) {
|
||||
keys, closeKeys, err := openBuilderKeys()
|
||||
if err != nil {
|
||||
return sdkdelivery.Module{}, false, err
|
||||
}
|
||||
defer closeKeys()
|
||||
v, err := keys.ModuleAt(ctx, module, "")
|
||||
if errors.Is(err, builderkeys.ErrNoValue) {
|
||||
return sdkdelivery.Module{}, false, nil
|
||||
}
|
||||
return v, err == nil, err
|
||||
}
|
||||
|
||||
// notHeld is where a module the catalogue does not hold stands: its case, the build request it was read
|
||||
// from, and the sentence that says it.
|
||||
type notHeld struct {
|
||||
@@ -96,7 +121,24 @@ type notHeld struct {
|
||||
}
|
||||
|
||||
// whereIs looks for a module the catalogue does not hold among the build requests the controller keeps.
|
||||
func whereIs(ctx context.Context, inv *inventory.Inventory, module string, now time.Time) (notHeld, error) {
|
||||
// notHeldReads is what whereIs reads of the records (*inventory.Inventory, or a test's).
|
||||
type notHeldReads interface {
|
||||
RequestsNamed(ctx context.Context, name string) ([]inventory.RequestOutcome, error)
|
||||
Catalogue(ctx context.Context) (map[string]catalogue.Manifest, error)
|
||||
RequestedNames(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
func whereIs(ctx context.Context, inv notHeldReads, module string, now time.Time) (notHeld, error) {
|
||||
// **Its key is coming** (ADR 0501 §6): the indexer holds it, and the controller registers it from the builder's done
|
||||
// key of it. Read before the build requests of before R4e, which nothing writes any more.
|
||||
if v, held, err := indexedModule(ctx, module); err != nil {
|
||||
return notHeld{}, fmt.Errorf("whether the indexer holds %s cannot be read: %w", module, err)
|
||||
} else if held {
|
||||
return notHeld{kind: keyComing, request: inventory.RequestOutcome{BuildRequest: inventory.BuildRequest{
|
||||
ID: keyPendingPrefix + module, Repository: v.Repository, Seat: gitSeat, Path: keyPath(v)}},
|
||||
said: fmt.Sprintf("%s is not registered yet: the indexer holds it in %s at %.8s, and it is registered from "+
|
||||
"the builder's done key of it, which no one asks", module, v.Repository, v.Commit)}, nil
|
||||
}
|
||||
requests, err := inv.RequestsNamed(ctx, module)
|
||||
if err != nil {
|
||||
return notHeld{}, err
|
||||
@@ -169,6 +211,12 @@ func notRegistered(ctx context.Context, open *stores, node, module string, opts
|
||||
return "", err
|
||||
}
|
||||
switch where.kind {
|
||||
case keyComing:
|
||||
lead := where.said
|
||||
if opts.Build {
|
||||
lead += "\n build \"true\" asks nothing: the builder builds every fingerprint the indexer writes (ADR 0501 §6)"
|
||||
}
|
||||
return keepPending(ctx, open, node, module, where.request, waiting, lead)
|
||||
case buildInFlight:
|
||||
lead := where.said
|
||||
if opts.Build {
|
||||
@@ -183,30 +231,20 @@ func notRegistered(ctx context.Context, open *stores, node, module string, opts
|
||||
also = fmt.Sprintf("\n %s already has a pending assignment of %s, waiting for build %s; build "+
|
||||
"\"true\" asks for a new build and makes it wait for that one", node, module, waiting.Build)
|
||||
}
|
||||
return "", fmt.Errorf("%w: %s%s\n assign it again with build \"true\" to ask for its build and keep this "+
|
||||
"assignment until the build registers it; or `build` it (repository %s, path %s) and assign it "+
|
||||
"once it is registered", inventory.ErrNoSuchModule, where.said, also, last.Repository, orRoot(last.Path))
|
||||
return "", fmt.Errorf("%w: %s%s\n it is registered from the builder's done key once a merge adds it to %s "+
|
||||
"(path %s) and the indexer holds it, or at the controller's terminal; assign it then",
|
||||
inventory.ErrNoSuchModule, where.said, also, last.Repository, orRoot(last.Path))
|
||||
}
|
||||
if waiting != nil && waiting.State == inventory.PendingApplying {
|
||||
return "", fmt.Errorf("%s is being assigned %s now; nothing was asked", node, module)
|
||||
}
|
||||
source := buildSource{Repository: last.Repository, Seat: last.Seat}
|
||||
id, err := askABuild(ctx, source, last.Path, last.Ref)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%s\n its build could not be asked for: %w; nothing was assigned", where.said, err)
|
||||
}
|
||||
asked := inventory.RequestOutcome{BuildRequest: inventory.BuildRequest{ID: id, Repository: last.Repository,
|
||||
Seat: last.Seat, Path: last.Path, Ref: last.Ref, For: "assign", At: now.UTC()}}
|
||||
recordBuildRequest(ctx, inv, asked.BuildRequest)
|
||||
lead := fmt.Sprintf("%s\n build %s of %s is asked for now; %s can be assigned once it registers it",
|
||||
where.said, id, requestSource(asked), module)
|
||||
return keepPending(ctx, open, node, module, asked, waiting, lead)
|
||||
// **No build is asked** (ADR 0501 §5, §6): a module the indexer does not hold has no key coming.
|
||||
return "", fmt.Errorf("%w: %s\n build \"true\" asks no build: the controller asks none (ADR 0501 §5); a module "+
|
||||
"is registered from the builder's done key once a merge adds it to a repository the catalogue builds from, or "+
|
||||
"at the controller's terminal, and the indexer holds no %s", inventory.ErrNoSuchModule, where.said, module)
|
||||
}
|
||||
answer := strings.TrimPrefix(where.said, inventory.ErrNoSuchModule.Error()) +
|
||||
"\n a module in a repository is built with `build` (its repository and path), then assigned"
|
||||
"\n a module in a repository is registered from the builder's done key once a merge adds it, then assigned"
|
||||
if opts.Build {
|
||||
answer += "\n build \"true\" asks for nothing here: the controller has no build request saying where a " +
|
||||
"module of that name is"
|
||||
answer += "\n build \"true\" asks for nothing: the controller asks no build (ADR 0501 §5), and the indexer " +
|
||||
"holds no module of that name"
|
||||
}
|
||||
return "", fmt.Errorf("%w%s", inventory.ErrNoSuchModule, answer)
|
||||
}
|
||||
@@ -316,7 +354,7 @@ func clock(t time.Time) string { return t.Local().Format("2006-01-02 15:04:05 MS
|
||||
|
||||
// closestNames is up to three names near the one asked: registered modules and directories the controller
|
||||
// asked to build.
|
||||
func closestNames(ctx context.Context, inv *inventory.Inventory, name string) []string {
|
||||
func closestNames(ctx context.Context, inv notHeldReads, name string) []string {
|
||||
var candidates []string
|
||||
if shelf, err := inv.Catalogue(ctx); err == nil {
|
||||
for m := range shelf {
|
||||
@@ -535,6 +573,19 @@ func settlePending(ctx context.Context, open *stores, now time.Time) []string {
|
||||
}
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(p.Build, keyPendingPrefix) {
|
||||
why, err := keyPendingEnded(ctx, p.Module)
|
||||
if err != nil {
|
||||
say("the key %s waits for cannot be read: %v", p.Module, err)
|
||||
continue
|
||||
}
|
||||
if why != "" {
|
||||
if line := expirePending(ctx, inv, p, why); line != "" {
|
||||
said = append(said, line)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
requests, err := inv.RequestsNamed(ctx, p.Module)
|
||||
if err != nil {
|
||||
say("the build requests for %s could not be read: %v", p.Module, err)
|
||||
@@ -744,3 +795,46 @@ func withdrawPending(ctx context.Context, inv *inventory.Inventory, node, module
|
||||
}
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
// keyPendingEnded is why a pending assignment waiting for a module's key ends: the indexer no longer holds the module,
|
||||
// or the builder's key of its fingerprint failed; empty while it is coming. A variable so a test needs no bus.
|
||||
var keyPendingEnded = func(ctx context.Context, module string) (string, error) {
|
||||
keys, closeKeys, err := openBuilderKeys()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer closeKeys()
|
||||
v, err := keys.ModuleAt(ctx, module, "")
|
||||
if errors.Is(err, builderkeys.ErrNoValue) {
|
||||
return fmt.Sprintf("the indexer no longer holds %s", module), nil
|
||||
}
|
||||
if err != nil || v.FP2 == "" {
|
||||
return "", err
|
||||
}
|
||||
k, err := keys.Build(ctx, module, v.FP2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if k.Failed() {
|
||||
return k.Says(), nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// settlePendingOnKey assigns, now, every pending assignment of a module its done key just registered (ADR 0501 §6):
|
||||
// what an outcome of a build did for one waiting on that build.
|
||||
func settlePendingOnKey(ctx context.Context, open *stores, module string) []string {
|
||||
waiting, err := open.inventory.Pending(ctx, time.Time{})
|
||||
if err != nil {
|
||||
return []string{fmt.Sprintf("the pending assignments could not be read after %s was registered: %v", module, err)}
|
||||
}
|
||||
var said []string
|
||||
for _, p := range waiting {
|
||||
if p.Module == module {
|
||||
if line := applyPending(ctx, open, p, keyPendingPrefix+module); line != "" {
|
||||
said = append(said, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
return said
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
@@ -36,11 +37,13 @@ func aCatalogueMesh(t *testing.T) *stores {
|
||||
return open
|
||||
}
|
||||
|
||||
// mergeAdding keeps the build request a merge adding a module asked before R4e (issue 300): nothing asks one since (ADR
|
||||
// 0501 §6), and the pending assignments still read the ones kept. These tests are of what an assignment does with one.
|
||||
func mergeAdding(t *testing.T, open *stores, dir, commit string) {
|
||||
t.Helper()
|
||||
m := link.SourceMoved{Owner: "novox", Repo: "mesh-catalog", Base: "main", Commit: commit,
|
||||
Paths: []string{dir + "/module.json"}, ModuleDirs: []string{dir}, ModuleDirsSaid: true}
|
||||
if err := (following{open: open}).SourceMoved(t.Context(), m); err != nil {
|
||||
if err := open.inventory.RecordBuildRequest(t.Context(), inventory.BuildRequest{ID: "b-" + dir,
|
||||
Repository: "novox/mesh-catalog", Seat: "git", Path: dir, Ref: "main", Commit: commit, For: "merge",
|
||||
At: time.Now().UTC()}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -225,101 +228,10 @@ func TestTheConditionClearsWhenTheModuleIsAssignedLater(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignWithBuildAsksForAKnownModule(t *testing.T) {
|
||||
open := aCatalogueMesh(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
if err := inv.RecordBuildRequest(ctx, inventory.BuildRequest{ID: "build-old", Repository: "novox/mesh-catalog",
|
||||
Seat: "git", Path: "modules/sensors", Ref: "main", For: "build", At: time.Now().Add(-time.Hour)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-old", Repository: "novox/mesh-catalog", Ref: "main",
|
||||
Path: "modules/sensors", On: "anchor", Failed: "no space left on device"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
asked := asksWithPaths(t)
|
||||
said, err := assignWith(ctx, open, "laptop", assignOptions{Build: true}, "sensors")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(*asked) != 1 || (*asked)[0] != [3]string{"novox/mesh-catalog", "modules/sensors", "main"} {
|
||||
t.Fatalf("asked %v", *asked)
|
||||
}
|
||||
for _, want := range []string{"known and not registered", "no space left on device",
|
||||
"build b-modules/sensors of novox/mesh-catalog@main (modules/sensors) is asked for now", "kept as pending"} {
|
||||
if !strings.Contains(said, want) {
|
||||
t.Fatalf("the answer does not say %q:\n%s", want, said)
|
||||
}
|
||||
}
|
||||
pending := pendingOf(t, open)
|
||||
if len(pending) != 1 || pending[0].Build != "b-modules/sensors" {
|
||||
t.Fatalf("pending: %+v", pending)
|
||||
}
|
||||
requests, _ := inv.RequestsNamed(ctx, "sensors")
|
||||
if len(requests) != 2 || requests[0].ID != "b-modules/sensors" || requests[0].For != "assign" {
|
||||
t.Fatalf("the request is not kept as assign's: %+v", requests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignWithBuildRepointsTheWaitingAssignment(t *testing.T) {
|
||||
open := aCatalogueMesh(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
if err := inv.RecordBuildRequest(ctx, inventory.BuildRequest{ID: "build-old", Repository: "novox/mesh-catalog",
|
||||
Seat: "git", Path: "modules/sensors", Ref: "main", For: "merge", At: time.Now().Add(-time.Hour)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-old", Repository: "novox/mesh-catalog", Ref: "main",
|
||||
Path: "modules/sensors", On: "anchor", Failed: "boom"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Waiting on the failed build, its expiry not yet settled.
|
||||
if _, err := inv.RecordPending(ctx, inventory.PendingAssignment{Node: "laptop", Module: "sensors",
|
||||
Build: "build-old", Repository: "novox/mesh-catalog", Path: "modules/sensors"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := assign(ctx, open, "laptop", "sensors")
|
||||
if err == nil || !strings.Contains(err.Error(), "already has a pending assignment of sensors, waiting for build build-old") {
|
||||
t.Fatalf("known and not built with a pending assignment waiting: %v", err)
|
||||
}
|
||||
asked := asksWithPaths(t)
|
||||
said, err := assignWith(ctx, open, "laptop", assignOptions{Build: true}, "sensors")
|
||||
if err != nil || !strings.Contains(said, "which waited for build build-old, now waits for build b-modules/sensors") {
|
||||
t.Fatalf("assign with build: %q %v", said, err)
|
||||
}
|
||||
if len(*asked) != 1 {
|
||||
t.Fatalf("asked %v", *asked)
|
||||
}
|
||||
pending := pendingOf(t, open)
|
||||
if len(pending) != 1 || pending[0].Build != "b-modules/sensors" || pending[0].State != inventory.PendingWaiting {
|
||||
t.Fatalf("pending: %+v", pending)
|
||||
}
|
||||
// The new build in flight now: build "true" again asks nothing.
|
||||
if said, err := assignWith(ctx, open, "laptop", assignOptions{Build: true}, "sensors"); err != nil ||
|
||||
!strings.Contains(said, "no second build was asked") || len(*asked) != 1 {
|
||||
t.Fatalf("a second build true while the build runs: %q %v, asked %v", said, err, *asked)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAFailedAskIsNotABuildInFlight(t *testing.T) {
|
||||
open := aCatalogueMesh(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
asksOfEveryModule(t) // the one ask left is the builder's own (ADR 0453 §5): this test is of what a walk does around it
|
||||
was := askABuild
|
||||
askABuild = func(context.Context, buildSource, string, string) (string, error) {
|
||||
return "", errors.New("cannot submit a build: nats: timeout")
|
||||
}
|
||||
t.Cleanup(func() { askABuild = was })
|
||||
mergeAdding(t, open, "modules/sensors", "3da80a4b00aa")
|
||||
_, err := assign(ctx, open, "laptop", "sensors")
|
||||
if err == nil || !strings.Contains(err.Error(), "the merge that added it (3da80a4b) could not ask for its build: cannot submit") {
|
||||
t.Fatalf("a merge that could not ask: %v", err)
|
||||
}
|
||||
if len(pendingOf(t, open)) != 0 {
|
||||
t.Fatal("a pending assignment waits on a build never asked")
|
||||
}
|
||||
|
||||
if err := inv.RecordBuildRequest(ctx, inventory.BuildRequest{ID: "build-waited", Repository: "novox/mesh-catalog",
|
||||
Seat: "git", Path: "modules/gauges", Ref: "main", For: "build"}); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -327,7 +239,7 @@ func TestAFailedAskIsNotABuildInFlight(t *testing.T) {
|
||||
if err := inv.MarkNotAsked(ctx, "build-waited", "cannot submit a build: no responders"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = assign(ctx, open, "laptop", "gauges")
|
||||
_, err := assign(ctx, open, "laptop", "gauges")
|
||||
if err == nil || !strings.Contains(err.Error(), "build build-waited, asked by build, was not handed over") {
|
||||
t.Fatalf("a build not handed over: %v", err)
|
||||
}
|
||||
@@ -737,3 +649,31 @@ func TestABuildNoLongerWaitedForIsStillInFlight(t *testing.T) {
|
||||
t.Fatalf("past the bound: %+v", rows)
|
||||
}
|
||||
}
|
||||
|
||||
// A10 (ADR 0501 §6; issue 1851): an assignment of a module the indexer holds and the catalogue does not yet waits for
|
||||
// the builder's done key of it, asking nothing, and is made when that key registers the module.
|
||||
func TestAnAssignmentWaitsForTheKeyAndAsksNothing(t *testing.T) {
|
||||
open := aCatalogueMesh(t)
|
||||
ctx := t.Context()
|
||||
noBuildAsked(t)
|
||||
b := aBuilderOnTheBus(t)
|
||||
fp := fpOf('a')
|
||||
manifest := b.indexed(t, "sensors", "1", "novox/mesh-catalog", "modules/sensors", "3da80a4b00aa0000", fp)
|
||||
b.built(t, "sensors", "1", fp, manifest, sdkdelivery.BuildBuilding)
|
||||
said, err := assignWith(ctx, open, "laptop", assignOptions{Build: true}, "sensors")
|
||||
if err != nil || !strings.Contains(said, "registered from the builder's done key") || !strings.Contains(said, "kept as pending") {
|
||||
t.Fatalf("A10: the assignment of a module whose key is coming: %q %v", said, err)
|
||||
}
|
||||
if p := pendingOf(t, open); len(p) != 1 || p[0].Build != keyPendingPrefix+"sensors" {
|
||||
t.Fatalf("A10: pending %+v", p)
|
||||
}
|
||||
settlePending(ctx, open, time.Now().Add(time.Hour))
|
||||
if p := pendingOf(t, open); p[0].State != inventory.PendingWaiting {
|
||||
t.Fatalf("A10: a pending assignment waiting for a building key expired: %+v", p)
|
||||
}
|
||||
b.built(t, "sensors", "1", fp, manifest, sdkdelivery.BuildDone)
|
||||
takeNewModules(ctx, open, "sensors")
|
||||
if !slices.Contains(assignedTo(t, open, "laptop"), "sensors") {
|
||||
t.Fatalf("A10: the key registered sensors and the pending assignment was not made: %+v", pendingOf(t, open))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// A10 (ADR 0501 §6; issue 1851): a module the catalogue does not hold and the indexer does is one whose key is coming:
|
||||
// an assignment of it waits for that key, named by the module, with no build asked and no build request read.
|
||||
func TestAModuleTheIndexerHoldsIsWaitedForByItsKey(t *testing.T) {
|
||||
was := indexedModule
|
||||
indexedModule = func(_ context.Context, module string) (sdkdelivery.Module, bool, error) {
|
||||
return sdkdelivery.Module{Repository: "novox/mesh-catalog", Commit: "3da80a4b00aa", Path: "modules/" + module}, true, nil
|
||||
}
|
||||
t.Cleanup(func() { indexedModule = was })
|
||||
where, err := whereIs(t.Context(), noRequests{}, "sensors", time.Now())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if where.kind != keyComing || where.request.ID != keyPendingPrefix+"sensors" || where.request.Path != "modules/sensors" ||
|
||||
!strings.Contains(where.said, "registered from the builder's done key") {
|
||||
t.Fatalf("a module whose key is coming is read as %+v", where)
|
||||
}
|
||||
}
|
||||
|
||||
// noRequests is records with no build request and no module: what a mesh after R4e reads of a new module.
|
||||
type noRequests struct{}
|
||||
|
||||
func (noRequests) RequestsNamed(context.Context, string) ([]inventory.RequestOutcome, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (noRequests) Catalogue(context.Context) (map[string]catalogue.Manifest, error) { return nil, nil }
|
||||
func (noRequests) RequestedNames(context.Context) ([]string, error) { return nil, nil }
|
||||
Reference in New Issue
Block a user