A provider declaring bound is judged by its entries, any other by its own have, and only once every provision it depends on, its secret-<provision> first, is provided. The credential is the vault's own provision. One integration test runs the holder on a real bus over mesh-sdk's in-memory mesh: a want composes the key, the outcomes provide it, leaving the node asks. Pins mesh-sdk 6b8c694.
220 lines
7.1 KiB
Go
220 lines
7.1 KiB
Go
// mesh-provisioner: the provisioner's server, holder of the mesh-provisioner seat once for the mesh (novox/hq ADR
|
|
// 0349 clause 3, ADR 0570). A Go bundle served by a runtime of its own, on its own bus account (`runs-as`, ADR 0259
|
|
// §8), keeping mesh-provisioner_provisions and _plans (every provision and every plan, composed from the bus on change
|
|
// events, its alone), _registry (which domain answers which kind of placeholder, its alone), and _applied and
|
|
// _collect, which each machine's provisioner client writes for its own machine; _needs is retired and emptied at its
|
|
// start. stdout is the MCP channel; what this module says, it says on stderr.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/asks"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// Seat is the role this module holds, once for the mesh, and Verb its one verb.
|
|
const (
|
|
Seat = "mesh-provisioner"
|
|
Module = "mesh-provisioner"
|
|
Verb = "provisioning"
|
|
)
|
|
|
|
func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) }
|
|
|
|
// runtimeStore is the bus, through the runtime that launched this bundle.
|
|
type runtimeStore struct{}
|
|
|
|
func (runtimeStore) Get(state, key string) (*Entry, error) {
|
|
e, err := stdio.State(state).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return &Entry{Key: e.Key, Value: e.Value, Revision: e.Revision}, nil
|
|
}
|
|
|
|
func (runtimeStore) Put(state, key string, value any) error {
|
|
_, err := stdio.State(state).Put(key, value)
|
|
return err
|
|
}
|
|
|
|
func (runtimeStore) Delete(state, key string) error { return stdio.State(state).Delete(key) }
|
|
|
|
func (runtimeStore) Keys(state string) ([]string, error) { return stdio.State(state).Keys() }
|
|
|
|
// runtimeAsks is the operator's channel through the runtime: asks published under this module's own name, and the
|
|
// router's record of one read under that name.
|
|
type runtimeAsks struct{}
|
|
|
|
func (runtimeAsks) Publish(subject string, body any, id string) error {
|
|
return stdio.SeatPublish(subject, body, id)
|
|
}
|
|
|
|
func (runtimeAsks) Record(id string) ([]byte, bool, error) {
|
|
e, err := stdio.SeatRecord("", id)
|
|
if err != nil || e == nil {
|
|
return nil, false, err
|
|
}
|
|
return e.Value, true, nil
|
|
}
|
|
|
|
// work is the nodes a change touched, composed one at a time by one worker: no timer, a change's event is the wake.
|
|
type work struct {
|
|
mu sync.Mutex
|
|
pending map[string]bool
|
|
wake chan struct{}
|
|
}
|
|
|
|
func (w *work) add(nodes ...string) {
|
|
if len(nodes) == 0 {
|
|
return
|
|
}
|
|
w.mu.Lock()
|
|
for _, n := range nodes {
|
|
w.pending[n] = true
|
|
}
|
|
w.mu.Unlock()
|
|
select {
|
|
case w.wake <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func (w *work) run(h *Holder) {
|
|
for range w.wake {
|
|
w.mu.Lock()
|
|
nodes := sortedKeys(w.pending)
|
|
w.pending = map[string]bool{}
|
|
w.mu.Unlock()
|
|
for _, n := range nodes {
|
|
c, err := h.ComposeNode(context.Background(), n)
|
|
switch {
|
|
case err != nil:
|
|
logf("[%s] %s is not composed: %v", Module, n, err)
|
|
case len(c.Plans)+len(c.Gone)+len(c.Changed)+len(c.Asked)+len(c.Problems) > 0:
|
|
logf("[%s] composed %s: %+v", Module, n, c)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// watched asks a watch of the runtime again until it is taken: the runtime answers only once Serve runs.
|
|
func watched(state string, handle func(stdio.StateChange) error) {
|
|
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
|
|
time.Sleep(wait)
|
|
if err := stdio.State(state).Watch("", handle); err != nil {
|
|
logf("[%s] %s is not watched yet (%v); asking again in %s", Module, state, err, wait)
|
|
continue
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
s := runtimeStore{}
|
|
h := &Holder{Store: s, Compose: FromBus(s), Asks: runtimeAsks{}, Now: time.Now, Logf: logf}
|
|
w := &work{pending: map[string]bool{}, wake: make(chan struct{}, 1)}
|
|
go w.run(h)
|
|
go starting(h)
|
|
go watched(PlacementRead, func(c stdio.StateChange) error { w.add(NodesOfPlacement(c.Key)...); return nil })
|
|
go watched(WantsRead, func(c stdio.StateChange) error { w.add(NodesOfWant(c.Key)...); return nil })
|
|
go watched(ModulesRead, func(c stdio.StateChange) error {
|
|
if module, err := delivery.ParseModulesKey(c.Key); err == nil {
|
|
w.add(h.NodesOfModule(module)...)
|
|
}
|
|
return nil
|
|
})
|
|
for _, provider := range BoundReads {
|
|
go watched(boundRead(provider), func(c stdio.StateChange) error {
|
|
if said, err := h.Rejudge(c.Key); err != nil {
|
|
logf("[%s] %s is not judged again: %v", Module, c.Key, err)
|
|
} else if said != "" {
|
|
logf("[%s] %s", Module, said)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
go watched(Registry, func(c stdio.StateChange) error {
|
|
if c.Op != "put" {
|
|
if _, _, err := Publish(s); err != nil {
|
|
logf("[%s] the registry is not written back: %v", Module, err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
go func() {
|
|
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
|
|
time.Sleep(wait)
|
|
err := stdio.Subscribe(asks.Seat+".decided."+Module, func(stdio.Envelope) error {
|
|
for _, said := range h.Decide() {
|
|
logf("[%s] %s", Module, said)
|
|
}
|
|
return nil
|
|
})
|
|
if err == nil {
|
|
return
|
|
}
|
|
logf("[%s] does not hear the operator's answers yet (%v); asking again in %s", Module, err, wait)
|
|
}
|
|
}()
|
|
if err := stdio.Serve("", append(Tools(s), holderTools(h)...)); err != nil {
|
|
logf("%v", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// starting writes the registry, deletes the retired needs and reads the operator's answers once the runtime answers,
|
|
// each tried again until it lands.
|
|
func starting(h *Holder) {
|
|
<-stdio.Served()
|
|
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
|
|
written, deleted, err := Publish(h.Store)
|
|
if err == nil {
|
|
if len(written)+len(deleted) > 0 {
|
|
logf("[%s] the registry: wrote %v, deleted %v", Module, written, deleted)
|
|
}
|
|
break
|
|
}
|
|
logf("[%s] the registry is not written yet (%v); trying again in %s", Module, err, wait)
|
|
time.Sleep(wait)
|
|
}
|
|
if n, err := h.RetireNeeds(); err != nil {
|
|
logf("[%s] the retired needs are not all deleted: %v", Module, err)
|
|
} else if n > 0 {
|
|
logf("[%s] deleted %d key(s) of the retired needs", Module, n)
|
|
}
|
|
for _, said := range h.Decide() {
|
|
logf("[%s] %s", Module, said)
|
|
}
|
|
}
|
|
|
|
// Tools is the seat's verb as the runtime serves it.
|
|
func Tools(s Store) []stdio.Tool {
|
|
return []stdio.Tool{{
|
|
Name: Seat + "." + Verb,
|
|
Description: "Read what the provisioner keeps (novox/hq ADR 0349 clause 3): the registry (which domain " +
|
|
"answers which kind of placeholder, the default), the needs each module on each node recorded, what is " +
|
|
"applied where, or the items of every collection, narrowed by node and module, and collect by " +
|
|
"collection. Reads only: each node's provisioner client writes its own node's needs, applied and items.",
|
|
Input: map[string]any{"type": "object", "properties": map[string]any{
|
|
"what": map[string]any{"type": "string", "enum": []string{Registry, Needs, Applied, Collect},
|
|
"description": "registry when not given"},
|
|
"node": map[string]any{"type": "string", "description": "a node"},
|
|
"module": map[string]any{"type": "string", "description": "a module: for collect, a contributor"},
|
|
"collection": map[string]any{"type": "string", "description": "for collect: a collection"},
|
|
}},
|
|
Run: func(args map[string]any) (any, error) {
|
|
q, err := queryOf(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Do(s, q)
|
|
},
|
|
}}
|
|
}
|