The provisioner client's account stays named while the client is held: a held client keeps the node's handing (issue 774)
provisionerAccount reads the user of the client's process from this pass's plan, and a client held as its stub (or carried as it runs) composes no process: on ace, 2026-11-04 01:29 to 01:33, the node's own composition named no account, the node-engine refused every filled write, and no module was handed (postgres's consumers, restic, power). A node now carries the account its client runs as (Node.RunningProvisioner: its own composition's account as it runs, else the user of the client's running process), and a placed client that composes none this pass keeps it. A row holds the client in the provisioned mesh, both ways: the account stays and restic keeps its filled files; both were lost before. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -234,6 +234,7 @@ func input(ctx context.Context, r Reader, mesh Mesh, s Scope, refuse func(module
|
|||||||
}
|
}
|
||||||
in, err := b.finish(ctx, r, r.Build, mesh, s, b.in, refuse, wanted)
|
in, err := b.finish(ctx, r, r.Build, mesh, s, b.in, refuse, wanted)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
runningProvisioner(&in, s.Node, s.Running)
|
||||||
// What runs is kept while a manifest does not yet say what shared code no longer infers (ADR 0601 §4).
|
// What runs is kept while a manifest does not yet say what shared code no longer infers (ADR 0601 §4).
|
||||||
for _, k := range keepWhatRuns(&in, s.Node, s.Running) {
|
for _, k := range keepWhatRuns(&in, s.Node, s.Running) {
|
||||||
if s.Kept != nil {
|
if s.Kept != nil {
|
||||||
@@ -1231,3 +1232,38 @@ func withoutHeldData(in compose.Input, node string) compose.Input {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// runningProvisioner names, on node, the account its provisioner client runs as now (compose.Node.RunningProvisioner): the
|
||||||
|
// account its own composition as it runs names, else the user of the running process of the module that runs the client.
|
||||||
|
func runningProvisioner(in *compose.Input, node string, running []delivery.Composed) {
|
||||||
|
account := ""
|
||||||
|
for _, c := range running {
|
||||||
|
if c.Module == "" && c.Own.Provisioner != "" {
|
||||||
|
account = c.Own.Provisioner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, c := range running {
|
||||||
|
if account != "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
man, ok := in.Catalogue[c.Module]
|
||||||
|
if c.Module == "" || !ok || !man.ClaimsSeat(compose.NodeProvisionerSeat) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, raw := range c.Resources {
|
||||||
|
var r struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
User string `json:"user"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal(raw, &r) == nil && r.Type == "process" && r.User != "" && r.User != "root" {
|
||||||
|
account = r.User
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range in.Nodes {
|
||||||
|
if in.Nodes[i].Name == node {
|
||||||
|
in.Nodes[i].RunningProvisioner = account
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -204,6 +204,48 @@ func TestTheProvisionedMeshHandsAPlanWithItsProvision(t *testing.T) {
|
|||||||
if fetched-archives != 1 {
|
if fetched-archives != 1 {
|
||||||
t.Errorf("restic's archive was read %d times over two compositions, want once", fetched-archives)
|
t.Errorf("restic's archive was read %d times over two compositions, want once", fetched-archives)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 774 (ace, 2026-11-04 01:29): the provisioner client held as its stub (its build not done, so the node keeps
|
||||||
|
// running what it runs) keeps the account the node's own composition names, and restic stays handed to it with its
|
||||||
|
// plan; without it every filled write was refused and no module was handed.
|
||||||
|
if b, err = Provisioned(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fps, ownKey := FPs(), ""
|
||||||
|
ownKey, _ = delivery.ComposedNodeKey(Node)
|
||||||
|
resticKey, _ := delivery.ComposedKey(Node, "restic", fps["restic"])
|
||||||
|
before, _, _, err := frombus.Compose(context.Background(), b, frombus.Mesh{}, frombus.Scope{Node: Node}, fps)
|
||||||
|
account := before[ownKey].Own.Provisioner
|
||||||
|
if err != nil || account == "" || len(before[resticKey].Filled) == 0 {
|
||||||
|
t.Fatalf("the node composes with its client: account %q, restic's filled files %v: %v", account, before[resticKey].Filled, err)
|
||||||
|
}
|
||||||
|
var running []delivery.Composed
|
||||||
|
for _, c := range before {
|
||||||
|
running = append(running, c)
|
||||||
|
}
|
||||||
|
for _, own := range []bool{true, false} {
|
||||||
|
runs := running
|
||||||
|
if !own { // what runs names no account of its own: the client's running process says it
|
||||||
|
runs = nil
|
||||||
|
for _, c := range running {
|
||||||
|
if c.Module == "" {
|
||||||
|
c.Own.Provisioner = ""
|
||||||
|
}
|
||||||
|
runs = append(runs, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.Builds["mesh-provisioner-client "+fps["mesh-provisioner-client"]] = &delivery.BuilderBuild{State: delivery.BuildBuilding}
|
||||||
|
after, _, held, err := frombus.Compose(context.Background(), b, frombus.Mesh{}, frombus.Scope{Node: Node, Running: runs}, fps)
|
||||||
|
if _, isHeld := held["mesh-provisioner-client"]; err != nil || !isHeld {
|
||||||
|
t.Fatalf("the client with no done build is not held: %v %v", held, err)
|
||||||
|
}
|
||||||
|
if got := after[ownKey].Own.Provisioner; got != account {
|
||||||
|
t.Errorf("the client held (account from its own composition: %v): the node's own names %q, want %q", own, got, account)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(after[resticKey].Filled, before[resticKey].Filled) {
|
||||||
|
t.Errorf("the client held (account from its own composition: %v): restic's filled files %v, want %v", own, after[resticKey].Filled, before[resticKey].Filled)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// statesOf is the bus b as a module's read states hold it.
|
// statesOf is the bus b as a module's read states hold it.
|
||||||
|
|||||||
@@ -224,6 +224,11 @@ type Node struct {
|
|||||||
// so it is left to the node: Compositions composes it as a stub (Stub) and carries its resources as they run, each
|
// so it is left to the node: Compositions composes it as a stub (Stub) and carries its resources as they run, each
|
||||||
// after the resource it runs after (issue 1126). Nil composes every folded module from its manifest, as before.
|
// after the resource it runs after (issue 1126). Nil composes every folded module from its manifest, as before.
|
||||||
RunsOwn *delivery.Composed
|
RunsOwn *delivery.Composed
|
||||||
|
// RunningProvisioner is the account the provisioner's client runs as on this node now, read from what runs (its own
|
||||||
|
// composition's provisioner account, else the user of the provisioner client's running process), empty when nothing
|
||||||
|
// says it. A client held as its stub this pass composes no process, so the node keeps this account and its handing
|
||||||
|
// (provisionerAccount; issue 774: ace, 2026-11-04 01:29, every filled write refused and postgres's consumers unhanded).
|
||||||
|
RunningProvisioner string
|
||||||
// Held is the modules refused alone on this node, each composed as what runs of it (Carried, in Kept; its Stub when
|
// Held is the modules refused alone on this node, each composed as what runs of it (Carried, in Kept; its Stub when
|
||||||
// nothing of it runs) so the rest is composed (novox/hq issue 1126): none of a held module's own composition is in what
|
// nothing of it runs) so the rest is composed (novox/hq issue 1126): none of a held module's own composition is in what
|
||||||
// Compositions answers, so the node keeps what it last applied of it; one whose resources the node's own runs
|
// Compositions answers, so the node keeps what it last applied of it; one whose resources the node's own runs
|
||||||
|
|||||||
@@ -74,6 +74,12 @@ func (c *composer) places(nodeName string, resolved Resolved) (Placed, error) {
|
|||||||
// not sent the field they would refuse.
|
// not sent the field they would refuse.
|
||||||
r.Hands = takesHanded(node)
|
r.Hands = takesHanded(node)
|
||||||
r.Provisioner = provisionerAccount(plan)
|
r.Provisioner = provisionerAccount(plan)
|
||||||
|
if r.Provisioner == "" && provisionerPlaced(plan) {
|
||||||
|
// The provisioner's client is placed here but composes no account this pass (held as its stub, which runs no
|
||||||
|
// process of its own composition): it keeps running as it runs, so the account it runs as stays named, and every
|
||||||
|
// module stays handed to it.
|
||||||
|
r.Provisioner = node.RunningProvisioner
|
||||||
|
}
|
||||||
r.Assigned = node.Handed
|
r.Assigned = node.Handed
|
||||||
r.ReadsHealth = node.Stated && node.Contract >= ReadinessContract
|
r.ReadsHealth = node.Stated && node.Contract >= ReadinessContract
|
||||||
r.JudgesRoot = node.Stated && node.Contract >= RootContract
|
r.JudgesRoot = node.Stated && node.Contract >= RootContract
|
||||||
@@ -190,6 +196,21 @@ func provisionerAccount(plan catalogue.Resolution) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// provisionerPlaced says the plan places a module claiming the provisioner client's seat on the machine, whether or not
|
||||||
|
// it composes the client's process this pass.
|
||||||
|
func provisionerPlaced(plan catalogue.Resolution) bool {
|
||||||
|
for _, m := range plan.Modules {
|
||||||
|
if m.ClaimsSeat(NodeProvisionerSeat) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunsTheProvisionerClient says a module runs its machine's provisioner client (runsTheProvisionerClient), for a reader
|
||||||
|
// of what runs.
|
||||||
|
func RunsTheProvisionerClient(m catalogue.Manifest) bool { return runsTheProvisionerClient(m) }
|
||||||
|
|
||||||
// runsTheProvisionerClient says a module runs its machine's provisioner client: it claims the seat and declares the own
|
// runsTheProvisionerClient says a module runs its machine's provisioner client: it claims the seat and declares the own
|
||||||
// secret that account is delivered in.
|
// secret that account is delivered in.
|
||||||
func runsTheProvisionerClient(m catalogue.Manifest) bool {
|
func runsTheProvisionerClient(m catalogue.Manifest) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user