Merge commit 'a3fea910' into feat/47-G-fix11-hrevert
This commit is contained in:
@@ -11,9 +11,12 @@
|
||||
// checkout or `git archive` of the catalogue repository at a ref: the command fetches nothing). -node adds a machine to
|
||||
// compose; -declarations answers each machine's declared paths with their owners and modes.
|
||||
//
|
||||
// It opens no network connection, bus, store or file beyond its standard input, the -request file and the -catalogue
|
||||
// directory, and reads
|
||||
// no environment. Exit codes: 0 the verdict is written (a machine that does not compose is a verdict, not an error);
|
||||
// Run as a pipeline step it reads its input, a composer.Request at depth compose, from the file named by the environment
|
||||
// variable MESH_RUN_INPUT and answers the run's output as its composer.Verdict on the last line of standard output
|
||||
// (design 47 G A-167).
|
||||
//
|
||||
// It opens no network connection, bus, store or file beyond its standard input, the -request file, the file
|
||||
// MESH_RUN_INPUT names and the -catalogue directory, and reads no other environment. Exit codes: 0 the verdict is written (a machine that does not compose is a verdict, not an error);
|
||||
// 1 the request cannot be read or run, said on standard error; 2 the flags are wrong.
|
||||
package main
|
||||
|
||||
@@ -65,6 +68,11 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
||||
}
|
||||
var raw []byte
|
||||
var err error
|
||||
// A pipeline step hands its input as a file named by MESH_RUN_INPUT (design 47 G A-167); -request, when given, wins.
|
||||
runInput := os.Getenv("MESH_RUN_INPUT")
|
||||
if *requestFile == "" && runInput != "" {
|
||||
*requestFile = runInput
|
||||
}
|
||||
if *requestFile != "" {
|
||||
raw, err = os.ReadFile(*requestFile)
|
||||
} else {
|
||||
|
||||
@@ -3,8 +3,8 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
"go/build"
|
||||
"os"
|
||||
@@ -267,6 +267,31 @@ func TestTheRequestIsReadFromAFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A pipeline step names its input file in MESH_RUN_INPUT: the command reads the request there and the verdict is the last
|
||||
// line of standard output (design 47 G A-167).
|
||||
func TestThePipelineStepReadsMESH_RUN_INPUT(t *testing.T) {
|
||||
file := filepath.Join(t.TempDir(), "input.json")
|
||||
r := composer.Request{V: composer.Version, Depth: composer.DepthCompose, Cases: []composer.Case{{Name: "only", Manifests: map[string]composer.Manifest{
|
||||
"demo": {Raw: json.RawMessage(`{"module":"demo","version":"1"}`)}}}}}
|
||||
if err := os.WriteFile(file, encode(t, r), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("MESH_RUN_INPUT", file)
|
||||
var out, errOut bytes.Buffer
|
||||
if code := run(nil, bytes.NewReader(nil), &out, &errOut); code != 0 {
|
||||
t.Fatalf("exit %d: %s", code, errOut.String())
|
||||
}
|
||||
lines := strings.Split(strings.TrimRight(out.String(), "\n"), "\n")
|
||||
var v composer.Verdict
|
||||
if err := json.Unmarshal([]byte(lines[len(lines)-1]), &v); err != nil || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted {
|
||||
t.Fatalf("last line is no verdict: %v\n%s", err, out.String())
|
||||
}
|
||||
t.Setenv("MESH_RUN_INPUT", file+".gone")
|
||||
if code := run(nil, bytes.NewReader(nil), &out, &errOut); code != 1 || !strings.Contains(errOut.String(), "cannot be read") {
|
||||
t.Errorf("a missing input file: exit %d, %q", code, errOut.String())
|
||||
}
|
||||
}
|
||||
|
||||
// The exit codes: 1 for a request that cannot be read or run, 2 for wrong flags, each said on standard error.
|
||||
func TestTheExitCodesSayWhatFailed(t *testing.T) {
|
||||
unfilled := encode(t, composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "c", Manifests: map[string]composer.Manifest{"x": {Digest: "abc"}}}}})
|
||||
@@ -334,7 +359,7 @@ func TestTheCommandImportsNoBusStoreNetworkOrProcess(t *testing.T) {
|
||||
for path, imports := range own {
|
||||
for _, imp := range imports {
|
||||
std := !strings.Contains(strings.SplitN(imp, "/", 2)[0], ".")
|
||||
if !std && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-sdk/go") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-contracts/") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/contracts/") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/sdk/composer") && imp != "git.novox.be/novox/mesh-delivery/sdk/catalogkey" && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds") && !strings.HasPrefix(imp, "github.com/novox/mesh-host/") {
|
||||
if !std && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-sdk/go") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-contracts/") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/") && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/composer") && imp != "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/catalogkey" && !strings.HasPrefix(imp, "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds") && !strings.HasPrefix(imp, "github.com/novox/mesh-host/") {
|
||||
t.Errorf("%s imports %s, which is neither the standard library, mesh-sdk nor this repository", path, imp)
|
||||
}
|
||||
for _, f := range forbidden {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildimage"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildnode"
|
||||
"github.com/novox/mesh-host/internal/apply"
|
||||
)
|
||||
|
||||
|
||||
@@ -1402,7 +1402,15 @@ func writeHealthReport(ctx context.Context, queue *link.Session, h link.Health,
|
||||
if c := machinesNow.Load(); c != nil {
|
||||
machines.ByAddress, machines.Hub = c.Machines()
|
||||
}
|
||||
_, err := queue.WriteNodeHealth(ctx, link.NodeHealthOf(queue.Membership.Node, h, peers, machines))
|
||||
report := link.NodeHealthOf(queue.Membership.Node, h, peers, machines)
|
||||
if queue.Fallback != nil && report.Fallback == nil {
|
||||
report.Fallback = queue.Fallback() // node-account-fallback (ADR 0583 §6), as the written report carries it
|
||||
}
|
||||
_, err := queue.WriteNodeHealth(ctx, report)
|
||||
// The findings the report says are raised by the engine here, at every look, written to the bus or not (design 47 G A-197).
|
||||
if keeper := ownFindingsNow.Load(); keeper != nil {
|
||||
keeper.Report(report, machines.ByAddress)
|
||||
}
|
||||
switch {
|
||||
case err != nil && err.Error() != *unwritten:
|
||||
*unwritten = err.Error()
|
||||
|
||||
@@ -616,6 +616,11 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
// The marks of the haves run off the composition pass, each bounded (see marker).
|
||||
marks := newMarker(run)
|
||||
heldFindings := &seatwait.HeldKeeper{Node: node, ObserverOf: findings.observer, Writer: findings, Ctx: run, Say: say}
|
||||
// And what only this engine knows of itself, raised where it knows it (design 47 G A-197): its haves' failed phase, health
|
||||
// and kept words as they are written, and each part of its health report at every look.
|
||||
ownFindings := &seatwait.NodeKeeper{Node: node, ObserverOf: findings.observer, Writer: findings, Ctx: run, Say: say}
|
||||
session.SetHaveSaid(ownFindings.Have, ownFindings.HaveGone)
|
||||
ownFindingsNow.Store(ownFindings)
|
||||
hooks := composerHooks{
|
||||
entryNotFound: func(fp string, notFound bool) {
|
||||
if err := seatwait.SayEntryNotFound(run, findings, node, findings.observer(), fp, notFound, time.Now); err != nil {
|
||||
@@ -707,6 +712,8 @@ func nodeAgent(ctx context.Context, opts options, signer ed25519.PublicKey, trus
|
||||
cancel()
|
||||
current.set(nil)
|
||||
composerNow.Store(nil)
|
||||
ownFindingsNow.Store(nil)
|
||||
session.SetHaveSaid(nil, nil)
|
||||
session.SetManifestOf(nil)
|
||||
apply.UseEngineModule(nil)
|
||||
shadowNow.Store(nil)
|
||||
@@ -909,6 +916,10 @@ func busyModules() []string {
|
||||
// (writeHealthReport); nil on a node that does not compose its own, whose parts then name no target.
|
||||
var machinesNow atomic.Pointer[selfcompose.Composer]
|
||||
|
||||
// ownFindingsNow is the keeper of the findings this engine raises of itself in the run that composes the node's own now, for the
|
||||
// health loop (writeHealthReport); nil while no run holds one.
|
||||
var ownFindingsNow atomic.Pointer[seatwait.NodeKeeper]
|
||||
|
||||
// composerNow is the composer of the run that composes the node's own now, nil while the node applies the controller's
|
||||
// compositions or no run is going: what the verb compose asks a pass of (issue 774).
|
||||
var composerNow atomic.Pointer[selfcompose.Composer]
|
||||
|
||||
@@ -12,11 +12,11 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
builderbuilds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
@@ -124,7 +124,7 @@ func newFindingsOfTheNode(session *link.Session, node, module string, say func(s
|
||||
Module: module,
|
||||
Node: node,
|
||||
State: link.FindingsState{Session: session},
|
||||
Kinds: []string{seatwait.Kind, seatwait.HeldKind, seatwait.EntryNotFoundKind},
|
||||
Kinds: append([]string{seatwait.Kind, seatwait.HeldKind, seatwait.EntryNotFoundKind}, seatwait.NodeKinds...),
|
||||
Asked: condseat.SubjectFindingsAsked,
|
||||
Logf: func(format string, args ...any) { say(fmt.Sprintf(format, args...)) },
|
||||
Publish: session.PublishEvent,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -8,19 +8,19 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshnodeenrollerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildimage v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/nodebuildnode v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
@@ -49,6 +49,6 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-networking/modules/mesh-connectivity-contracts/go/seats/theprivatenetwork v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
"github.com/novox/mesh-host/internal/judge"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshrecovery"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
)
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
vault "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
wants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
catalog "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
wants "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ package bucketstest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
)
|
||||
|
||||
// Version is the value version the holders write.
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
|
||||
|
||||
@@ -2,7 +2,7 @@ package compose
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
@@ -45,9 +45,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
|
||||
|
||||
@@ -15,9 +15,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
"maps"
|
||||
"os"
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"errors"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"github.com/novox/mesh-host/internal/compose"
|
||||
"github.com/novox/mesh-host/internal/compose/frombus"
|
||||
"strings"
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"github.com/novox/mesh-host/internal/compose"
|
||||
"github.com/novox/mesh-host/internal/compose/resolution"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
@@ -19,9 +19,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/compose"
|
||||
"github.com/novox/mesh-host/internal/compose/resolution"
|
||||
|
||||
@@ -12,15 +12,15 @@ import (
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/meshmoduleassignerclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
sdk "git.novox.be/novox/mesh-delivery/sdk/composer"
|
||||
sdk "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/composer"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
"git.novox.be/novox/mesh-sdk/go/sdkversion"
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
"git.novox.be/novox/mesh-sdk/go/keys"
|
||||
|
||||
@@ -3,7 +3,7 @@ package judge
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -440,6 +440,9 @@ func (q *Session) writeHaves(ctx context.Context, bus Bus) {
|
||||
s.written = map[string]string{}
|
||||
}
|
||||
s.written[module] = f.Build
|
||||
if say, _ := q.haveSaid(); say != nil {
|
||||
said = append(said, func() { say(module, h) })
|
||||
}
|
||||
if ev, ok := s.runningToSay(module, h, q.manifestOf()); ok {
|
||||
said = append(said, func() { q.sayRunning(ev) })
|
||||
}
|
||||
@@ -555,6 +558,9 @@ func (q *Session) writeRemoved(ctx context.Context, b HaveBucket, node string, s
|
||||
switch {
|
||||
case errors.Is(err, ErrHaveAbsent):
|
||||
delete(s.removed, module) // nothing to say: the key is not there
|
||||
if _, gone := q.haveSaid(); gone != nil {
|
||||
*said = append(*said, func() { gone(module) })
|
||||
}
|
||||
continue
|
||||
case err != nil:
|
||||
q.sayHave(fmt.Sprintf("could not read %s in the have bucket, and wrote nothing more there: %v", key, err))
|
||||
@@ -563,6 +569,9 @@ func (q *Session) writeRemoved(ctx context.Context, b HaveBucket, node string, s
|
||||
}
|
||||
if c, err := nodeengineclient.DecodeHave(current); err == nil && c.Phase == nodeengineclient.PhaseRemoved {
|
||||
delete(s.removed, module)
|
||||
if _, gone := q.haveSaid(); gone != nil {
|
||||
*said = append(*said, func() { gone(module) })
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := b.Set(ctx, key, raw, revision); err != nil {
|
||||
@@ -571,6 +580,9 @@ func (q *Session) writeRemoved(ctx context.Context, b HaveBucket, node string, s
|
||||
return false
|
||||
}
|
||||
delete(s.removed, module)
|
||||
if _, gone := q.haveSaid(); gone != nil {
|
||||
*said = append(*said, func() { gone(module) })
|
||||
}
|
||||
// The module no longer runs here: running says stopped, with no fingerprint, on its own subject (design 47 G §365, §404).
|
||||
delete(s.runningSaid, module)
|
||||
*said = append(*said, func() { q.sayRunning(stoppedRunning(node, module)) })
|
||||
@@ -983,6 +995,20 @@ func (q *Session) sayRunning(ev nodeengine.Running) {
|
||||
q.sayEngineOn(nodeengine.SubjectRunning(ev.Node, ev.Module), "running", ev)
|
||||
}
|
||||
|
||||
// SetHaveSaid gives the session what it tells of each module's have as it writes it (said), and of a module whose have says
|
||||
// it is gone (gone): the node-engine's findings of itself are made of them (design 47 G A-197). Either may be nil.
|
||||
func (q *Session) SetHaveSaid(said func(module string, h nodeengine.HaveByNodeModuleValue), gone func(module string)) {
|
||||
q.haveSay.Lock()
|
||||
q.haveSaidFn, q.haveGoneFn = said, gone
|
||||
q.haveSay.Unlock()
|
||||
}
|
||||
|
||||
func (q *Session) haveSaid() (func(string, nodeengine.HaveByNodeModuleValue), func(string)) {
|
||||
q.haveSay.Lock()
|
||||
defer q.haveSay.Unlock()
|
||||
return q.haveSaidFn, q.haveGoneFn
|
||||
}
|
||||
|
||||
// SetManifestOf gives the session what names the manifest of the build a module runs (the composer's picture of the
|
||||
// catalog half): its digest, or "" when the node does not know it.
|
||||
func (q *Session) SetManifestOf(f func(module, fp string) string) {
|
||||
|
||||
@@ -230,7 +230,13 @@ func aHaveQueue(l *haveLink) (*Session, *[]string) {
|
||||
func TestAnAppliedModuleIsWrittenRunningAndAFailedOneFailed(t *testing.T) {
|
||||
l := newHaveLink()
|
||||
q, _ := aHaveQueue(l)
|
||||
// Design 47 G A-197: each have as written is told to the node-engine's findings of itself, the failed one with its detail.
|
||||
told := map[string]nodeengine.HaveByNodeModuleValue{}
|
||||
q.SetHaveSaid(func(m string, h nodeengine.HaveByNodeModuleValue) { told[m] = h }, nil)
|
||||
q.SayFacts(context.Background(), appliedModules())
|
||||
if told["b"].Phase != nodeengineclient.PhaseFailed || !strings.Contains(told["b"].Detail, "no space") || told["a"].Phase != nodeengineclient.PhaseRunning {
|
||||
t.Errorf("the haves told were %+v", told)
|
||||
}
|
||||
a, b := l.have(t, "n1.a"), l.have(t, "n1.b")
|
||||
if a.Phase != nodeengineclient.PhaseRunning || a.Build != "b-1" || a.Health != nodeengineclient.HealthOK {
|
||||
t.Errorf("a: %+v", a)
|
||||
@@ -565,9 +571,14 @@ func onlyA() Report {
|
||||
func TestAModuleLeavingTheDeclarationIsWrittenRemoved(t *testing.T) {
|
||||
l := newHaveLink()
|
||||
q, _ := aHaveQueue(l)
|
||||
var gone []string
|
||||
q.SetHaveSaid(nil, func(m string) { gone = append(gone, m) })
|
||||
q.SayFacts(context.Background(), appliedModules())
|
||||
revA, revB := l.kv["n1.a"].rev, l.kv["n1.b"].rev
|
||||
q.SayFacts(context.Background(), onlyA())
|
||||
if len(gone) != 1 || gone[0] != "b" { // design 47 G A-197: a module that left ends its findings
|
||||
t.Errorf("the modules told gone were %v, want b", gone)
|
||||
}
|
||||
b := l.have(t, "n1.b")
|
||||
if b.Phase != nodeengineclient.PhaseRemoved || b.Build != "b-2" {
|
||||
t.Errorf("b: %+v", b)
|
||||
|
||||
@@ -29,6 +29,10 @@ type Session struct {
|
||||
// Own is the module that is this node-engine (§527, §634): its discovery, finding writer and vault login names read it.
|
||||
// Nil names none, as a test's session does.
|
||||
Own *OwnName
|
||||
// haveSay guards haveSaidFn and haveGoneFn (SetHaveSaid).
|
||||
haveSay sync.Mutex
|
||||
haveSaidFn func(module string, h nodeengine.HaveByNodeModuleValue)
|
||||
haveGoneFn func(module string)
|
||||
// Fallback is the engine's fall back to its old login its health report carries now (Rotator.Fallback); nil says none.
|
||||
Fallback func() *nodeengine.LoginFallback
|
||||
// linkedUser is the bus user the link open now authenticated as, guarded by mu (WhenLinkedAs).
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package registryforms
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
containerimageregistry "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
ociartifactregistry "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/containerimageregistryclient/reference"
|
||||
containerimageregistry "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/containerimageregistry"
|
||||
ociartifactregistry "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/ociartifactregistry"
|
||||
)
|
||||
|
||||
// Forms reads, records and routes a reference in the registry seat's form and the older artifact-store one.
|
||||
|
||||
@@ -0,0 +1,574 @@
|
||||
// NodeKeeper keeps the findings only the node-engine knows of itself (design 47 G A-197, ADR 0596, A-149): a have's failed
|
||||
// phase (apply-failing) and its health (module-unhealthy), what it keeps of a module as it runs (kept-as-it-runs), and each
|
||||
// part of its own health report (machine-network, network-rewritten, network-unreachable, machine-units, relogin-needed,
|
||||
// directory-used-as-found, node-account-fallback), under the kind words the watchman raised them under. Each is raised and
|
||||
// cleared where the engine knows the fact, through its finding writer, never read back from the have or the report by
|
||||
// another part.
|
||||
//
|
||||
// The keeper holds the facts it was told (the haves, the last report) and, at every telling, makes the findings of this
|
||||
// node those the facts say: what the writer holds of its kinds for this node and no fact says is cleared, what a fact says
|
||||
// and the writer does not hold, or holds in other words, is written. Nothing runs on a timer: a bound (the apply interval,
|
||||
// the minute before a failing health is said, the four hours before it is urgent) is judged at each telling, and the engine
|
||||
// tells at every pass over its haves and every look at its health.
|
||||
package seatwait
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
)
|
||||
|
||||
// The kinds the node-engine raises of itself, the words the watchman raised them under.
|
||||
const (
|
||||
KindApplyFailing = "apply-failing"
|
||||
KindModuleUnhealthy = "module-unhealthy"
|
||||
KindKeptAsRuns = "kept-as-it-runs"
|
||||
KindMachineNetwork = "machine-network"
|
||||
KindNetworkRewritten = "network-rewritten"
|
||||
KindNetworkUnreachable = "network-unreachable"
|
||||
KindMachineUnits = "machine-units"
|
||||
KindReloginNeeded = "relogin-needed"
|
||||
KindUsedAsFound = "directory-used-as-found"
|
||||
KindAccountFallback = "node-account-fallback"
|
||||
)
|
||||
|
||||
// NodeKinds is every kind the keeper writes, for the finding writer's list of kinds.
|
||||
var NodeKinds = []string{KindApplyFailing, KindModuleUnhealthy, KindKeptAsRuns, KindMachineNetwork, KindNetworkRewritten,
|
||||
KindNetworkUnreachable, KindMachineUnits, KindReloginNeeded, KindUsedAsFound, KindAccountFallback}
|
||||
|
||||
// The bounds the facts are judged by.
|
||||
const (
|
||||
// ApplyEvery is how long a have may keep saying failed before the node says it does not apply its update.
|
||||
ApplyEvery = 5 * time.Minute
|
||||
// UnhealthyConfirmAfter is how long a have must keep saying its health failed before the module is said unhealthy.
|
||||
UnhealthyConfirmAfter = time.Minute
|
||||
// UnhealthyUrgentAfter is how long a module stays unhealthy before it is urgent.
|
||||
UnhealthyUrgentAfter = 4 * time.Hour
|
||||
// ControlNodeModule is the module whose have names the control node: a failing network there is urgent.
|
||||
ControlNodeModule = "nats"
|
||||
// PackageManagerKey is how a node-engine names a package manager that does not answer among what failed.
|
||||
PackageManagerKey = "the package manager ("
|
||||
)
|
||||
|
||||
const (
|
||||
partResolvConf = "resolv-conf"
|
||||
partNames = "names"
|
||||
partTunnel = "tunnel"
|
||||
partBus = "bus"
|
||||
stateUnhealthy = "unhealthy"
|
||||
phaseFailed = nodeengineclient.PhaseFailed
|
||||
)
|
||||
|
||||
// NodeKeeper keeps the findings of one node-engine. Tell it with Have, HaveGone and Report; every telling reconciles.
|
||||
type NodeKeeper struct {
|
||||
Node string
|
||||
Observer string
|
||||
// ObserverOf, when set, answers the observer at each write (see Waiter).
|
||||
ObserverOf func() string
|
||||
Writer Writer
|
||||
// Ctx is the run's context for the writer's acts; nil is the background.
|
||||
Ctx context.Context
|
||||
// Now is the clock; nil is time.Now.
|
||||
Now func() time.Time
|
||||
// Say tells the node's journal; nil says nothing.
|
||||
Say func(string)
|
||||
|
||||
mu sync.Mutex
|
||||
haves map[string]nodeengine.HaveByNodeModuleValue
|
||||
failedSince map[string]time.Time
|
||||
sickSince map[string]time.Time
|
||||
keptSince map[string]time.Time
|
||||
report *nodeengine.HealthByNodeValue
|
||||
byAddress map[string]string
|
||||
}
|
||||
|
||||
// Have is a module's have as the node-engine wrote it.
|
||||
func (k *NodeKeeper) Have(module string, h nodeengine.HaveByNodeModuleValue) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
if k.haves == nil {
|
||||
k.haves, k.failedSince, k.sickSince, k.keptSince = map[string]nodeengine.HaveByNodeModuleValue{}, map[string]time.Time{},
|
||||
map[string]time.Time{}, map[string]time.Time{}
|
||||
}
|
||||
if h.Phase == nodeengineclient.PhaseRemoved {
|
||||
k.forget(module)
|
||||
k.reconcile()
|
||||
return
|
||||
}
|
||||
k.haves[module] = h
|
||||
now := k.now()
|
||||
setSince(k.failedSince, module, h.Phase == phaseFailed, now)
|
||||
setSince(k.sickSince, module, h.Health == nodeengineclient.HealthFailed, now)
|
||||
setSince(k.keptSince, module, len(h.Kept) > 0, now)
|
||||
k.reconcile()
|
||||
}
|
||||
|
||||
// HaveGone is a module no longer held by the node.
|
||||
func (k *NodeKeeper) HaveGone(module string) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
k.forget(module)
|
||||
k.reconcile()
|
||||
}
|
||||
|
||||
// Report is the node's health report as it stands now (written or not), and the machines by the address of each, for the
|
||||
// peers whose tunnel it does not see. Told at every look, so a bound is judged then.
|
||||
func (k *NodeKeeper) Report(h nodeengine.HealthByNodeValue, byAddress map[string]string) {
|
||||
k.mu.Lock()
|
||||
defer k.mu.Unlock()
|
||||
if h.At.IsZero() {
|
||||
h.At = k.now()
|
||||
}
|
||||
k.report, k.byAddress = &h, byAddress
|
||||
k.reconcile()
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) forget(module string) {
|
||||
delete(k.haves, module)
|
||||
delete(k.failedSince, module)
|
||||
delete(k.sickSince, module)
|
||||
delete(k.keptSince, module)
|
||||
}
|
||||
|
||||
func setSince(m map[string]time.Time, module string, on bool, now time.Time) {
|
||||
if !on {
|
||||
delete(m, module)
|
||||
} else if _, ok := m[module]; !ok {
|
||||
m[module] = now
|
||||
}
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) now() time.Time {
|
||||
if k.Now != nil {
|
||||
return k.Now()
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) say(s string) {
|
||||
if k.Say != nil {
|
||||
k.Say(s)
|
||||
}
|
||||
}
|
||||
|
||||
func stampSec(t time.Time) string { return t.UTC().Format("2006-01-02 15:04:05 MST") }
|
||||
|
||||
func firstLine(s string) string {
|
||||
line, _, _ := strings.Cut(s, "\n")
|
||||
return line
|
||||
}
|
||||
|
||||
func orWords(s, or string) string {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return or
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ownKey is the identity of a finding the keeper writes.
|
||||
func ownKey(scope, id, kind string) string { return scope + "." + id + "." + kind }
|
||||
|
||||
// reconcile makes this node's findings of the keeper's kinds those the facts say. The caller holds the lock.
|
||||
func (k *NodeKeeper) reconcile() {
|
||||
ctx := k.Ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
want := map[string]condseat.Finding{}
|
||||
add := func(f condseat.Finding) { want[ownKey(f.Scope, f.ID, f.Kind)] = f }
|
||||
k.haveFindings(add)
|
||||
k.reportFindings(add)
|
||||
held := map[string]condseat.Finding{}
|
||||
for _, f := range k.Writer.Held() {
|
||||
if k.owns(f) {
|
||||
held[ownKey(f.Scope, f.ID, f.Kind)] = f
|
||||
}
|
||||
}
|
||||
for _, key := range slices.Sorted(maps.Keys(held)) {
|
||||
if _, still := want[key]; still {
|
||||
continue
|
||||
}
|
||||
f := held[key]
|
||||
if err := k.Writer.Clear(ctx, f.Scope, f.ID, f.Kind, "the node-engine no longer finds it so"); err != nil {
|
||||
k.say(fmt.Sprintf("the finding %s of %s cannot be cleared: %v", f.Kind, k.Node, err))
|
||||
}
|
||||
}
|
||||
for _, key := range slices.Sorted(maps.Keys(want)) {
|
||||
f := want[key]
|
||||
if was, ok := held[key]; ok {
|
||||
f.Since, f.At = was.Since, was.At // the same thing seen again writes the same words
|
||||
if sameFinding(was, f) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := k.Writer.Write(ctx, f); err != nil {
|
||||
k.say(fmt.Sprintf("the finding %s of %s cannot be written: %v", f.Kind, k.Node, err))
|
||||
continue
|
||||
}
|
||||
k.say(f.Summary)
|
||||
}
|
||||
}
|
||||
|
||||
// owns says a held finding is one the keeper writes for this node.
|
||||
func (k *NodeKeeper) owns(f condseat.Finding) bool {
|
||||
if !slices.Contains(NodeKinds, f.Kind) {
|
||||
return false
|
||||
}
|
||||
if f.Kind == KindNetworkUnreachable {
|
||||
return strings.HasSuffix(f.ID, "."+k.Node+".unreachable")
|
||||
}
|
||||
return f.Subject.Machine == k.Node
|
||||
}
|
||||
|
||||
func sameFinding(a, b condseat.Finding) bool {
|
||||
if a.Summary != b.Summary || a.Severity != b.Severity || a.Headline != b.Headline || a.Needs != b.Needs ||
|
||||
a.Explanation != b.Explanation || !slices.Equal(a.Subject.Also, b.Subject.Also) || len(a.Evidence) != len(b.Evidence) {
|
||||
return false
|
||||
}
|
||||
for i := range a.Evidence {
|
||||
if a.Evidence[i].Said != b.Evidence[i].Said {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// finding is a finding of this node's: observed by the engine's own module, about a machine of the scope.
|
||||
//
|
||||
// The id and ref are the condition's key the watchman raised it under, `<scope>.<id>.<token>` (A-14): a token other than the kind
|
||||
// goes into the id as `<id>.<ref>` and is kept in ref, as the watchman's findings were written.
|
||||
func (k *NodeKeeper) finding(scope, id, kind, ref, machine string, severity string, since time.Time, summary, said string) condseat.Finding {
|
||||
since = since.UTC()
|
||||
if ref != "" {
|
||||
id += "." + ref
|
||||
}
|
||||
f := condseat.Finding{Observer: observerOf(k.Observer, k.ObserverOf), Kind: kind, Scope: scope, ID: id, Severity: severity,
|
||||
Ref: ref, Source: kind, Summary: summary, At: since, Since: since,
|
||||
Subject: condseat.Subject{Scope: scope, ID: id, Machine: machine}}
|
||||
if said != "" {
|
||||
f.Evidence = []condseat.Evidence{{At: since, Said: said}}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// haveFindings are the findings the haves say: apply-failing, module-unhealthy and kept-as-it-runs.
|
||||
func (k *NodeKeeper) haveFindings(add func(condseat.Finding)) {
|
||||
now := k.now()
|
||||
type failure struct{ module, words string }
|
||||
var failed []failure
|
||||
var since time.Time
|
||||
for _, m := range slices.Sorted(maps.Keys(k.failedSince)) {
|
||||
failed = append(failed, failure{m, firstLine(orWords(k.haves[m].Detail, "no reason given"))})
|
||||
if at := k.failedSince[m]; since.IsZero() || at.Before(since) {
|
||||
since = at
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 && now.Sub(since) > ApplyEvery {
|
||||
var names, words []string
|
||||
tool := ""
|
||||
for _, f := range failed {
|
||||
names = append(names, f.module)
|
||||
words = append(words, f.module+": "+f.words)
|
||||
w := strings.TrimPrefix(f.words, "refused: ")
|
||||
if i := strings.Index(w, PackageManagerKey); i >= 0 && tool == "" {
|
||||
tool = strings.TrimSuffix(strings.SplitN(w[i+len(PackageManagerKey):], ")", 2)[0], ")")
|
||||
}
|
||||
}
|
||||
f := k.finding(scopeMachine, k.Node, KindApplyFailing, "", k.Node, condseat.FindingSeverityWarning, since, "", strings.Join(words, "; "))
|
||||
f.Headline = k.Node + " is not applying its update"
|
||||
if tool != "" {
|
||||
f.Summary = fmt.Sprintf("%s: %s does not answer, so no package reaches it; everything else it was sent is applied, since %s (bound %s)",
|
||||
k.Node, tool, stampMin(since), ApplyEvery)
|
||||
f.Headline = k.Node + " cannot install packages"
|
||||
} else {
|
||||
f.Summary = fmt.Sprintf("%s has failed to apply %s since %s (bound %s)", k.Node, strings.Join(names, ", "), stampMin(since), ApplyEvery)
|
||||
f.Explanation = fmt.Sprintf("%s has failed to apply its updates, and trying again has not helped. The details say what fails.", k.Node)
|
||||
}
|
||||
add(f)
|
||||
}
|
||||
for _, m := range slices.Sorted(maps.Keys(k.sickSince)) {
|
||||
at := k.sickSince[m]
|
||||
if now.Sub(at) < UnhealthyConfirmAfter {
|
||||
continue
|
||||
}
|
||||
severity := condseat.FindingSeverityWarning
|
||||
if now.Sub(at) >= UnhealthyUrgentAfter {
|
||||
severity = condseat.FindingSeverityUrgent
|
||||
}
|
||||
h := k.haves[m]
|
||||
f := k.finding(scopeModule, m+"."+k.Node, KindModuleUnhealthy, "unhealthy", k.Node, severity, at,
|
||||
fmt.Sprintf("%s on %s is not healthy: %s", m, k.Node, orWords(h.Detail, "its check failed")), firstLine(orWords(h.Detail, "failed")))
|
||||
f.Headline = fmt.Sprintf("%s not working on %s", m, k.Node)
|
||||
add(f)
|
||||
}
|
||||
for _, m := range slices.Sorted(maps.Keys(k.keptSince)) {
|
||||
words := strings.Join(k.haves[m].Kept, "; ")
|
||||
f := k.finding(scopeModule, m+"."+k.Node, KindKeptAsRuns, "kept", k.Node, condseat.FindingSeverityWarning, k.keptSince[m],
|
||||
fmt.Sprintf("%s on %s is kept as it runs: %s", m, k.Node, words), firstLine(words))
|
||||
f.Headline = fmt.Sprintf("%s kept as it runs on %s", m, k.Node)
|
||||
f.Needs = "a build of " + m + " whose manifest says it (tool-runner, core-credentials)"
|
||||
add(f)
|
||||
}
|
||||
}
|
||||
|
||||
func stampMin(t time.Time) string { return t.UTC().Format("2006-01-02 15:04 MST") }
|
||||
|
||||
// control says the node runs the module the control node is named by.
|
||||
func (k *NodeKeeper) control() bool {
|
||||
h, ok := k.haves[ControlNodeModule]
|
||||
return ok && h.Phase != nodeengineclient.PhaseRemoved
|
||||
}
|
||||
|
||||
// reportFindings are the findings the node's health report says.
|
||||
func (k *NodeKeeper) reportFindings(add func(condseat.Finding)) {
|
||||
h := k.report
|
||||
if h == nil {
|
||||
return
|
||||
}
|
||||
node := k.Node
|
||||
var own []nodeengine.NetworkPart
|
||||
var rewritten *nodeengine.NetworkPart
|
||||
var parts []nodeengine.NetworkPart
|
||||
if h.Network != nil {
|
||||
for _, p := range h.Network.Parts {
|
||||
if p.State == stateUnhealthy {
|
||||
parts = append(parts, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
toward := map[string][]nodeengine.NetworkPart{}
|
||||
for i, p := range parts {
|
||||
switch {
|
||||
case p.Part == partResolvConf:
|
||||
rewritten = &parts[i]
|
||||
case len(p.Targets) > 0:
|
||||
for _, x := range p.Targets {
|
||||
toward[x] = append(toward[x], p)
|
||||
}
|
||||
default:
|
||||
own = append(own, p)
|
||||
}
|
||||
}
|
||||
if rewritten != nil {
|
||||
add(k.rewritten(*rewritten, parts))
|
||||
kept := own[:0]
|
||||
for _, p := range own {
|
||||
if p.Part != partNames {
|
||||
kept = append(kept, p)
|
||||
}
|
||||
}
|
||||
own = kept
|
||||
}
|
||||
if len(own) > 0 {
|
||||
var words, said []string
|
||||
severity := condseat.FindingSeverityWarning
|
||||
for _, p := range own {
|
||||
words = append(words, p.Reason)
|
||||
said = append(said, fmt.Sprintf("%s since %s: %s", p.Part, stampSec(p.Since), p.Said))
|
||||
if p.Part == partBus {
|
||||
severity = condseat.FindingSeverityUrgent
|
||||
}
|
||||
}
|
||||
if k.control() {
|
||||
severity = condseat.FindingSeverityUrgent
|
||||
}
|
||||
add(k.finding(scopeMachine, node, KindMachineNetwork, "network", node, severity, h.Network.Since,
|
||||
fmt.Sprintf("%s's network is not healthy: %s", node, strings.Join(words, "; ")), strings.Join(said, " | ")))
|
||||
}
|
||||
// A tunnel peer this node does not see points at that peer's machine as a tunnel part toward it would.
|
||||
for _, p := range h.Peers {
|
||||
if p.Seen {
|
||||
continue
|
||||
}
|
||||
for _, a := range p.Addresses {
|
||||
addr, _, _ := strings.Cut(a, "/")
|
||||
if x := k.byAddress[addr]; x != "" && x != node {
|
||||
toward[x] = append(toward[x], nodeengine.NetworkPart{Part: partTunnel, State: stateUnhealthy, Since: h.At,
|
||||
Said: "no handshake with " + x + " within five minutes", Targets: []string{x}})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, x := range slices.Sorted(maps.Keys(toward)) {
|
||||
var what, said []string
|
||||
since := toward[x][0].Since
|
||||
severity := condseat.FindingSeverityWarning
|
||||
for _, p := range toward[x] {
|
||||
wd := map[string]string{partTunnel: "the tunnel to it", partBus: "the bus on it", partNames: "its resolver"}[p.Part]
|
||||
if wd == "" {
|
||||
wd = p.Part
|
||||
}
|
||||
if !slices.Contains(what, wd) {
|
||||
what = append(what, wd)
|
||||
}
|
||||
said = append(said, fmt.Sprintf("%s: %s: %s", node, p.Part, p.Said))
|
||||
if p.Since.Before(since) {
|
||||
since = p.Since
|
||||
}
|
||||
if p.Part == partBus {
|
||||
severity = condseat.FindingSeverityUrgent
|
||||
}
|
||||
}
|
||||
f := k.finding(scopeMachine, x+"."+node, KindNetworkUnreachable, "unreachable", x, severity, since,
|
||||
fmt.Sprintf("%s cannot be reached from %s: %s", x, node, strings.Join(what, ", ")), strings.Join(said, " | "))
|
||||
f.Subject.Also = []string{node}
|
||||
add(f)
|
||||
}
|
||||
if f, ok := k.units(h.Units); ok {
|
||||
add(f)
|
||||
}
|
||||
for _, f := range k.relogin(h.Relogin) {
|
||||
add(f)
|
||||
}
|
||||
for _, f := range k.found(h.Found) {
|
||||
add(f)
|
||||
}
|
||||
if f := h.Fallback; f != nil && f.Node == node {
|
||||
said := fmt.Sprintf("%s fell back from its new login %s to its old one during a rotation of its bus account: %s", node, f.From, f.Why)
|
||||
g := k.finding(scopeMachine, node, KindAccountFallback, "", node, condseat.FindingSeverityWarning, f.At, said, said)
|
||||
g.Headline = fmt.Sprintf("%s fell back to its old bus login", node)
|
||||
g.Needs = fmt.Sprintf("abandon the rotation of %s's account, or rotate it again once its new login authenticates.", node)
|
||||
add(g)
|
||||
}
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) rewritten(p nodeengine.NetworkPart, all []nodeengine.NetworkPart) condseat.Finding {
|
||||
m := k.Node
|
||||
writer := ""
|
||||
if p.Writer != "" {
|
||||
writer = " (" + p.Writer + ")"
|
||||
}
|
||||
cost := "the names still resolve through what it wrote"
|
||||
said := []string{p.Part + ": " + p.Said}
|
||||
for _, q := range all {
|
||||
if q.Part == partNames {
|
||||
cost = strings.TrimSuffix(q.Reason, ".")
|
||||
said = append(said, q.Part+": "+q.Said)
|
||||
}
|
||||
}
|
||||
id := m
|
||||
summary := fmt.Sprintf("the resolver file on %s was rewritten by another program%s — %s until the node-engine writes it "+
|
||||
"back at its next reconcile, or that program gives it back", m, writer, cost)
|
||||
if p.Owner != "" {
|
||||
id = m + "." + p.Owner
|
||||
summary = fmt.Sprintf("the resolver file %s writes on %s was rewritten by another program%s — %s until the node-engine "+
|
||||
"writes it back at its next reconcile, or that program gives it back", p.Owner, m, writer, cost)
|
||||
}
|
||||
severity := condseat.FindingSeverityWarning
|
||||
if k.control() {
|
||||
severity = condseat.FindingSeverityUrgent
|
||||
}
|
||||
return k.finding(scopeMachine, id, KindNetworkRewritten, "rewritten", m, severity, p.Since, summary,
|
||||
fmt.Sprintf("since %s: %s", stampSec(p.Since), strings.Join(said, " | ")))
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) units(u *nodeengine.UnitsHealth) (condseat.Finding, bool) {
|
||||
if u == nil || len(u.Failed) == 0 {
|
||||
return condseat.Finding{}, false
|
||||
}
|
||||
node := k.Node
|
||||
var names, said []string
|
||||
since := u.Failed[0].Since
|
||||
for _, f := range u.Failed {
|
||||
where := ""
|
||||
if f.Scope == "user" {
|
||||
where = " (the account's own manager)"
|
||||
}
|
||||
names = append(names, f.Unit+where)
|
||||
scope, result := f.Scope, f.Result
|
||||
if scope == "" {
|
||||
scope = "system"
|
||||
}
|
||||
if result == "" {
|
||||
result = "no reason said"
|
||||
}
|
||||
line := fmt.Sprintf("%s in the %s manager: %s", f.Unit, scope, result)
|
||||
if f.Load != "" && f.Load != "loaded" {
|
||||
line += ", its unit " + f.Load
|
||||
}
|
||||
if f.Resource != "" {
|
||||
line += ", named by the mesh's own " + f.Resource
|
||||
}
|
||||
said = append(said, line+", since "+stampSec(f.Since))
|
||||
if f.Since.Before(since) {
|
||||
since = f.Since
|
||||
}
|
||||
}
|
||||
failed := "1 failed service"
|
||||
if len(u.Failed) > 1 {
|
||||
failed = fmt.Sprintf("%d failed services", len(u.Failed))
|
||||
}
|
||||
f := k.finding(scopeMachine, node, KindMachineUnits, "units", node, condseat.FindingSeverityWarning, since,
|
||||
fmt.Sprintf("%s's service manager is degraded: %d failed unit(s) no module places — %s. Each is the machine's own: "+
|
||||
"mend or remove it there, or have a module place it", node, len(u.Failed), strings.Join(names, ", ")),
|
||||
strings.Join(said, "; "))
|
||||
f.Headline = strings.ToUpper(failed[:1]) + failed[1:] + " on " + node
|
||||
f.Needs = fmt.Sprintf("mend or remove them on %s, or silence this if they do not matter.", node)
|
||||
return f, true
|
||||
}
|
||||
|
||||
func orModule(m string) string {
|
||||
if m == "" {
|
||||
return "_"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) relogin(accounts []nodeengine.NodeAccount) []condseat.Finding {
|
||||
by := map[string][]nodeengine.NodeAccount{}
|
||||
for _, a := range accounts {
|
||||
by[a.Module] = append(by[a.Module], a)
|
||||
}
|
||||
var out []condseat.Finding
|
||||
for _, module := range slices.Sorted(maps.Keys(by)) {
|
||||
var names []string
|
||||
since := by[module][0].Since
|
||||
for _, a := range by[module] {
|
||||
names = append(names, a.Account)
|
||||
if a.Since.Before(since) {
|
||||
since = a.Since
|
||||
}
|
||||
}
|
||||
sort.Strings(names)
|
||||
said := fmt.Sprintf("relogin needed on %s: %s waits for a new login of %s, which is in its group and whose login began "+
|
||||
"before it was; log out of %[1]s completely and log in again, or restart it", k.Node, orModule(module), strings.Join(names, ", "))
|
||||
out = append(out, k.finding(scopeModule, orModule(module)+"."+k.Node, KindReloginNeeded, "", k.Node, condseat.FindingSeverityWarning, since, said, said))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (k *NodeKeeper) found(dirs []nodeengine.NodeDirectory) []condseat.Finding {
|
||||
by := map[string][]nodeengine.NodeDirectory{}
|
||||
for _, d := range dirs {
|
||||
by[d.Module] = append(by[d.Module], d)
|
||||
}
|
||||
var out []condseat.Finding
|
||||
for _, module := range slices.Sorted(maps.Keys(by)) {
|
||||
var ids, why []string
|
||||
since := by[module][0].Since
|
||||
for _, d := range by[module] {
|
||||
ids = append(ids, d.Resource)
|
||||
why = append(why, d.Reason)
|
||||
if d.Since.Before(since) {
|
||||
since = d.Since
|
||||
}
|
||||
}
|
||||
m := orModule(module)
|
||||
said := fmt.Sprintf("%s on %s uses %s as found: %s", m, k.Node, strings.Join(ids, ", "), strings.Join(why, "; "))
|
||||
f := k.finding(scopeModule, m+"."+k.Node, KindUsedAsFound, "", k.Node, condseat.FindingSeverityWarning, since, said, said)
|
||||
f.Headline = fmt.Sprintf("%s waits for a directory on %s", m, k.Node)
|
||||
f.Explanation = fmt.Sprintf("Needs you: hand the directory over from the control-node, as the operator; the details name it "+
|
||||
"and the line to type. A directory of %s was already on %s, with another owner or mode than %s declares.", m, k.Node, m)
|
||||
f.Needs = "hand the directory over from the control-node, as the operator; the details name it and the line to type."
|
||||
out = append(out, f)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package seatwait
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
condseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
)
|
||||
|
||||
// Design 47 G A-197: what only the node-engine knows of itself it raises as its own findings, under the kind words the watchman
|
||||
// raised them under, and clears where it knows the fact no longer holds.
|
||||
func TestTheNodeEngineRaisesWhatOnlyItKnows(t *testing.T) {
|
||||
at := time.Date(2026, 11, 12, 8, 0, 0, 0, time.UTC)
|
||||
type step struct {
|
||||
do func(k *NodeKeeper, advance func(time.Duration))
|
||||
want []string // "scope.id.kind" of what stands after it, sorted
|
||||
}
|
||||
failed := nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseFailed, Detail: "refused: the package manager (pacman) does not answer"}
|
||||
sick := nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthFailed, Detail: "port closed"}
|
||||
report := func(h nodeengine.HealthByNodeValue) func(*NodeKeeper, func(time.Duration)) {
|
||||
return func(k *NodeKeeper, _ func(time.Duration)) { k.Report(h, map[string]string{"10.0.0.2": "g14"}) }
|
||||
}
|
||||
for name, steps := range map[string][]step{
|
||||
"a have failed past the apply interval is apply-failing, cleared by the clean apply": {
|
||||
{func(k *NodeKeeper, _ func(time.Duration)) { k.Have("b", failed) }, nil},
|
||||
{func(k *NodeKeeper, adv func(time.Duration)) { adv(ApplyEvery + time.Second); k.Have("b", failed) }, []string{"machine.ace.apply-failing"}},
|
||||
{func(k *NodeKeeper, _ func(time.Duration)) {
|
||||
k.Have("b", nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseRunning})
|
||||
}, nil},
|
||||
},
|
||||
"a have unhealthy past a minute is module-unhealthy, cleared when it is healthy or removed": {
|
||||
{func(k *NodeKeeper, _ func(time.Duration)) { k.Have("b", sick) }, nil},
|
||||
{func(k *NodeKeeper, adv func(time.Duration)) { adv(time.Minute); k.Have("b", sick) }, []string{"module.b.ace.unhealthy.module-unhealthy"}},
|
||||
{func(k *NodeKeeper, _ func(time.Duration)) {
|
||||
k.Have("b", nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseRemoved})
|
||||
}, nil},
|
||||
},
|
||||
"what a module keeps as it runs is kept-as-it-runs until its have says none": {
|
||||
{func(k *NodeKeeper, _ func(time.Duration)) {
|
||||
k.Have("b", nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseRunning, Kept: []string{"tool-runner"}})
|
||||
}, []string{"module.b.ace.kept.kept-as-it-runs"}},
|
||||
{func(k *NodeKeeper, _ func(time.Duration)) { k.HaveGone("b") }, nil},
|
||||
},
|
||||
"each part of the report is its own finding and each ends with the report": {
|
||||
{report(nodeengine.HealthByNodeValue{
|
||||
Network: &nodeengine.NetworkHealth{Since: at, Parts: []nodeengine.NetworkPart{
|
||||
{Part: "bus", State: "unhealthy", Reason: "no bus", Since: at},
|
||||
{Part: "tunnel", State: "unhealthy", Since: at, Targets: []string{"g14"}},
|
||||
{Part: "resolv-conf", State: "unhealthy", Owner: "dnsmasq", Since: at}}},
|
||||
Units: &nodeengine.UnitsHealth{Failed: []nodeengine.FailedUnit{{Unit: "x.service", Since: at}}},
|
||||
Relogin: []nodeengine.NodeAccount{{Account: "u", Module: "m", Since: at}},
|
||||
Found: []nodeengine.NodeDirectory{{Module: "m", Resource: "m.dir", Since: at}},
|
||||
Fallback: &nodeengine.LoginFallback{Node: "ace", From: "new", At: at}}),
|
||||
[]string{"machine.ace.dnsmasq.rewritten.network-rewritten", "machine.ace.network.machine-network", "machine.ace.node-account-fallback",
|
||||
"machine.ace.units.machine-units", "machine.g14.ace.unreachable.network-unreachable", "module.m.ace.directory-used-as-found",
|
||||
"module.m.ace.relogin-needed"}},
|
||||
{report(nodeengine.HealthByNodeValue{}), nil},
|
||||
},
|
||||
"a tunnel peer not seen is the machine unreachable": {
|
||||
{report(nodeengine.HealthByNodeValue{Peers: []nodeengine.PeerSeen{{Key: "k", Addresses: []string{"10.0.0.2/32"}}}}),
|
||||
[]string{"machine.g14.ace.unreachable.network-unreachable"}},
|
||||
{report(nodeengine.HealthByNodeValue{Peers: []nodeengine.PeerSeen{{Key: "k", Addresses: []string{"10.0.0.2/32"}, Seen: true}}}), nil},
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
now := at
|
||||
w := &fakeFindings{}
|
||||
// Another node's finding and an unrelated kind stand in the state and are never touched.
|
||||
w.held = []condseat.Finding{{Scope: "machine", ID: "g14", Kind: KindMachineUnits, Subject: condseat.Subject{Machine: "g14"}},
|
||||
{Scope: "machine", ID: "ace", Kind: HeldKind, Subject: condseat.Subject{Machine: "ace"}}}
|
||||
k := &NodeKeeper{Node: "ace", Observer: "mesh-host", Writer: w, Now: func() time.Time { return now }}
|
||||
for i, s := range steps {
|
||||
s.do(k, func(d time.Duration) { now = now.Add(d) })
|
||||
var got []string
|
||||
for _, f := range w.held {
|
||||
if f.Observer == "mesh-host" {
|
||||
got = append(got, f.Scope+"."+f.ID+"."+f.Kind)
|
||||
}
|
||||
}
|
||||
if strings.Join(sorted(got), ",") != strings.Join(s.want, ",") {
|
||||
t.Fatalf("step %d: stands %v, want %v", i, sorted(got), s.want)
|
||||
}
|
||||
}
|
||||
if len(w.held) != 2 {
|
||||
t.Fatalf("what is not the keeper's was touched: %+v", w.held)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sorted(in []string) []string {
|
||||
out := append([]string(nil), in...)
|
||||
for i := range out {
|
||||
for j := i + 1; j < len(out); j++ {
|
||||
if out[j] < out[i] {
|
||||
out[i], out[j] = out[j], out[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"maps"
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshdeliveryclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
"strings"
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
"slices"
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
)
|
||||
|
||||
// The node that keeps a kept build is the want's kept-on alone, for an artifact its parts name (design 47 G 526, A-50).
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-sdk/go/declaration"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/buckets/bucketstest"
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/meshmoduleassigner"
|
||||
nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshwants"
|
||||
"github.com/novox/mesh-host/internal/buckets"
|
||||
"github.com/novox/mesh-host/internal/compose/frombus"
|
||||
engcomp "github.com/novox/mesh-host/internal/compositions"
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
|
||||
"git.novox.be/novox/mesh-sdk/go/seal"
|
||||
"github.com/novox/mesh-host/internal/agent"
|
||||
|
||||
Reference in New Issue
Block a user