Join 10: the assigner reads its outside-wait settings from the files its manifest declares
The validator refused the manifest: a process's env is not filled from config. As A-112 says, a process reads each
setting from a file with the content ${config:<key>}, read at its start (the builder's form); a missing or empty file is
the default, a content that is no positive whole number stops the start, naming the file.
Builds: design 47 G §633, §706, A-112
Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -250,7 +250,11 @@ func (b *bed) startStoppable(t *testing.T) (<-chan error, func()) {
|
||||
if err := os.WriteFile(broker, cred, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env := map[string]string{"MESH_BROKER_FILE": broker, "MESH_ASSIGNER_OUTSIDE_SETTLE_SECONDS": "1"}
|
||||
settle := filepath.Join(b.dir, "outside-settle-seconds")
|
||||
if err := os.WriteFile(settle, []byte("1"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
env := map[string]string{"MESH_BROKER_FILE": broker, "MESH_ASSIGNER_OUTSIDE_SETTLE_FILE": settle}
|
||||
ctx, stop := context.WithCancel(b.ctx)
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
|
||||
@@ -526,7 +526,10 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
|
||||
// The wait on an outside cause (outside.go, design 47 G §706): the keeper's three events say when to judge again, and the
|
||||
// seat mesh-wants' wait-held and wait-ended are said; registered before the hub starts.
|
||||
if bus != nil && kvs[buckets.Waits] != nil {
|
||||
bound, settle := outsideSettings(getenv)
|
||||
bound, settle, err := outsideSettings(getenv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.outside = newOutsideActs(bus.CallKept, wake, logger.Printf, bound, settle)
|
||||
for _, subject := range []string{mcseat.SubjectConditionRaised, mcseat.SubjectConditionChanged, mcseat.SubjectConditionCleared} {
|
||||
subject := subject
|
||||
|
||||
@@ -3,7 +3,10 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -15,9 +18,9 @@ import (
|
||||
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
|
||||
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
|
||||
mcseat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
meshwants "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-assigner/asks"
|
||||
"git.novox.be/novox/mesh-delivery/sdk/catalogkey"
|
||||
"git.novox.be/novox/mesh-sdk/go/seatdef"
|
||||
"git.novox.be/novox/mesh-sdk/go/views"
|
||||
|
||||
@@ -94,15 +97,42 @@ func newOutsideActs(call func(string, any) (json.RawMessage, error), wake func()
|
||||
}
|
||||
|
||||
// outsideSettings are the bounds of the wait, the settings outside-wait-minutes and outside-settle-seconds (design 47 G §633, §706),
|
||||
// from the environment the manifest's config fills; zero for one not set, which is its default.
|
||||
func outsideSettings(getenv func(string) string) (bound, settle time.Duration) {
|
||||
if n, err := strconv.Atoi(strings.TrimSpace(getenv("MESH_ASSIGNER_OUTSIDE_WAIT_MINUTES"))); err == nil && n > 0 {
|
||||
bound = time.Duration(n) * time.Minute
|
||||
// each read at the start from the file the manifest declares with the content ${config:<key>}, which the environment names
|
||||
// (A-112, as the builder reads its own); zero for one whose file is missing or empty, which is its default, and an error
|
||||
// naming the file for a content that is no positive whole number.
|
||||
func outsideSettings(getenv func(string) string) (bound, settle time.Duration, err error) {
|
||||
minutes, err := settingFile(getenv("MESH_ASSIGNER_OUTSIDE_WAIT_FILE"))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
if n, err := strconv.Atoi(strings.TrimSpace(getenv("MESH_ASSIGNER_OUTSIDE_SETTLE_SECONDS"))); err == nil && n > 0 {
|
||||
settle = time.Duration(n) * time.Second
|
||||
seconds, err := settingFile(getenv("MESH_ASSIGNER_OUTSIDE_SETTLE_FILE"))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return bound, settle
|
||||
return time.Duration(minutes) * time.Minute, time.Duration(seconds) * time.Second, nil
|
||||
}
|
||||
|
||||
// settingFile reads one setting's file: 0 for no path, no file or an empty one.
|
||||
func settingFile(path string) (int, error) {
|
||||
if path == "" {
|
||||
return 0, nil
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return 0, nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v := strings.TrimSpace(string(raw))
|
||||
if v == "" {
|
||||
return 0, nil
|
||||
}
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 1 {
|
||||
return 0, fmt.Errorf("%s holds %q, which is not a positive whole number", path, v)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// readStart reads the open conditions of every machine once at the start, for the machines whose own network is held.
|
||||
|
||||
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -209,6 +211,28 @@ func TestTheWaitOnAnOutsideCause(t *testing.T) {
|
||||
t.Errorf("events %v", r.events)
|
||||
}
|
||||
})
|
||||
t.Run("its bounds are read at the start from the files the manifest declares (A-112)", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
write := func(name, content string) string {
|
||||
p := filepath.Join(dir, name)
|
||||
if err := os.WriteFile(p, []byte(content), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
env := map[string]string{"MESH_ASSIGNER_OUTSIDE_WAIT_FILE": write("wait", "45\n"), "MESH_ASSIGNER_OUTSIDE_SETTLE_FILE": write("settle", "7")}
|
||||
if bound, settle, err := outsideSettings(func(k string) string { return env[k] }); err != nil || bound != 45*time.Minute || settle != 7*time.Second {
|
||||
t.Errorf("bound %v settle %v err %v, want 45m and 7s", bound, settle, err)
|
||||
}
|
||||
env["MESH_ASSIGNER_OUTSIDE_WAIT_FILE"], env["MESH_ASSIGNER_OUTSIDE_SETTLE_FILE"] = filepath.Join(dir, "missing"), write("empty", "")
|
||||
if bound, settle, err := outsideSettings(func(k string) string { return env[k] }); err != nil || bound != 0 || settle != 0 {
|
||||
t.Errorf("bound %v settle %v err %v, want both defaults", bound, settle, err)
|
||||
}
|
||||
env["MESH_ASSIGNER_OUTSIDE_SETTLE_FILE"] = write("bad", "soon")
|
||||
if _, _, err := outsideSettings(func(k string) string { return env[k] }); err == nil || !strings.Contains(err.Error(), "bad") {
|
||||
t.Errorf("err %v, want one naming the file", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAMachineWhoseOwnNetworkIsOpenGetsNoWant(t *testing.T) {
|
||||
|
||||
@@ -142,7 +142,8 @@ func TestManifest(t *testing.T) {
|
||||
"seat:operator-channel.asks"}; !slices.Equal(slices.Sorted(slices.Values(m.Invokes)), want) { // ADR 0668 §4: the ended asks at bind
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
// design 47 G §633, §706: the bounds of the wait on an outside cause are `config` keys filling the process's environment.
|
||||
// design 47 G §633, §706, A-112: the bounds of the wait on an outside cause are `config` keys, each filling a file the
|
||||
// manifest declares, which the process reads at its start; the environment names only the files.
|
||||
var raw struct {
|
||||
Config map[string]struct {
|
||||
Default any `json:"default"`
|
||||
@@ -152,9 +153,17 @@ func TestManifest(t *testing.T) {
|
||||
if err := json.Unmarshal(file, &raw); err != nil || raw.Config["outside-wait-minutes"].Default != float64(30) || raw.Config["outside-settle-seconds"].Default != float64(90) || len(raw.Config) != 2 {
|
||||
t.Errorf("config = %+v (%v), want outside-wait-minutes of 30 and outside-settle-seconds of 90", raw.Config, err)
|
||||
}
|
||||
if !strings.Contains(string(file), `"MESH_ASSIGNER_OUTSIDE_WAIT_MINUTES": "${config:outside-wait-minutes}"`) ||
|
||||
!strings.Contains(string(file), `"MESH_ASSIGNER_OUTSIDE_SETTLE_SECONDS": "${config:outside-settle-seconds}"`) {
|
||||
t.Errorf("the process's env does not carry the two settings from config")
|
||||
for _, f := range []struct{ env, path, key string }{
|
||||
{"MESH_ASSIGNER_OUTSIDE_WAIT_FILE", "${dir:state}/outside-wait-minutes", "outside-wait-minutes"},
|
||||
{"MESH_ASSIGNER_OUTSIDE_SETTLE_FILE", "${dir:state}/outside-settle-seconds", "outside-settle-seconds"},
|
||||
} {
|
||||
if !strings.Contains(string(file), `"`+f.env+`": "`+f.path+`"`) || !strings.Contains(string(file), `"path": "`+f.path+`"`) ||
|
||||
!strings.Contains(string(file), `"content": "${config:`+f.key+`}"`) {
|
||||
t.Errorf("the setting %s is not a file %s the env names as %s", f.key, f.path, f.env)
|
||||
}
|
||||
}
|
||||
if strings.Contains(string(file), `": "${config:outside-`) && strings.Contains(string(file), `"MESH_ASSIGNER_OUTSIDE_WAIT_MINUTES"`) {
|
||||
t.Errorf("the process's env still carries a setting")
|
||||
}
|
||||
if want := []string{"assignments-copied", "put", "views.asks", "views.copies", "views.differences", "views.findings", "views.assignments", "views.seat-holders", "views.wants"}; !slices.Equal(m.Emits, want) {
|
||||
t.Errorf("emits = %v, want the copy, put and its six views", m.Emits)
|
||||
|
||||
@@ -180,6 +180,22 @@
|
||||
"place": ".",
|
||||
"unknown": "program"
|
||||
},
|
||||
{
|
||||
"id": "outside-wait",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/outside-wait-minutes",
|
||||
"owner": "mesh-delivery-assigner",
|
||||
"mode": "0600",
|
||||
"content": "${config:outside-wait-minutes}"
|
||||
},
|
||||
{
|
||||
"id": "outside-settle",
|
||||
"type": "file",
|
||||
"path": "${dir:state}/outside-settle-seconds",
|
||||
"owner": "mesh-delivery-assigner",
|
||||
"mode": "0600",
|
||||
"content": "${config:outside-settle-seconds}"
|
||||
},
|
||||
{
|
||||
"id": "assigner",
|
||||
"type": "process",
|
||||
@@ -190,8 +206,8 @@
|
||||
],
|
||||
"env": {
|
||||
"MESH_BROKER_FILE": "${dir:state}/broker",
|
||||
"MESH_ASSIGNER_OUTSIDE_WAIT_MINUTES": "${config:outside-wait-minutes}",
|
||||
"MESH_ASSIGNER_OUTSIDE_SETTLE_SECONDS": "${config:outside-settle-seconds}"
|
||||
"MESH_ASSIGNER_OUTSIDE_WAIT_FILE": "${dir:state}/outside-wait-minutes",
|
||||
"MESH_ASSIGNER_OUTSIDE_SETTLE_FILE": "${dir:state}/outside-settle-seconds"
|
||||
},
|
||||
"user": "mesh-delivery-assigner",
|
||||
"health": {
|
||||
|
||||
Reference in New Issue
Block a user