Node-engine: reads the seat list through the seat mesh-catalog, holds what is new until it was read, writes its finding after five minutes; verbs seat-list and seat-list-again; whole-module case against a stand-in of the seat (H-7, ADR 0636 §1, §7, §8; issue 1966)
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
// FindingsBucket is the node-engine's state `findings`: the findings this engine writes in its own bucket, one key each,
|
||||
// which the condition keeper raises as conditions and clears when the key goes (novox/hq ADR 0564 §2, ADR 0636 §1).
|
||||
const FindingsBucket = "mesh-host_findings"
|
||||
|
||||
func (l *natsLink) findingsKV() (nats.KeyValue, error) {
|
||||
kv, err := l.js.KeyValue(FindingsBucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the node-engine's state %s cannot be bound (it is created from the node-engine's manifest; "+
|
||||
"written again once it is): %w", FindingsBucket, err)
|
||||
}
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
func (q *Session) findings() (nats.KeyValue, error) {
|
||||
q.init()
|
||||
q.mu.Lock()
|
||||
bus := q.bus
|
||||
q.mu.Unlock()
|
||||
l, ok := bus.(interface{ findingsKV() (nats.KeyValue, error) })
|
||||
if bus == nil || !ok {
|
||||
return nil, ErrNoLink
|
||||
}
|
||||
return l.findingsKV()
|
||||
}
|
||||
|
||||
// PutFinding writes one finding of this engine over the link open now.
|
||||
func (q *Session) PutFinding(key string, value []byte) error {
|
||||
kv, err := q.findings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = kv.Put(key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteFinding clears one finding of this engine over the link open now; a finding already gone is cleared.
|
||||
func (q *Session) DeleteFinding(key string) error {
|
||||
kv, err := q.findings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = kv.Delete(key); err != nil && !errors.Is(err, nats.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o OverNATS) findingsKV() (nats.KeyValue, error) {
|
||||
kv, err := o.JS.KeyValue(FindingsBucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the node-engine's state %s cannot be bound: %w", FindingsBucket, err)
|
||||
}
|
||||
return kv, nil
|
||||
}
|
||||
@@ -51,6 +51,11 @@ const (
|
||||
ActComposeCandidate = nodeengine.VerbComposeCandidate
|
||||
// ActManifestReads reads the reader capabilities this engine states (novox/hq ADR 0618 §3).
|
||||
ActManifestReads = nodeengine.VerbManifestReads
|
||||
// ActSeatList reads the seat list this engine holds and what it waits for; ActSeatListAgain makes it read the list now
|
||||
// (novox/hq ADR 0636 §7). The node-engine seat's definition carries both verbs, which answer only the operator and the
|
||||
// healers.
|
||||
ActSeatList = "seat-list"
|
||||
ActSeatListAgain = "seat-list-again"
|
||||
)
|
||||
|
||||
// The states of an act: asked, done and refused are also the seat's events.
|
||||
@@ -149,6 +154,9 @@ type HandActs struct {
|
||||
// Packages answers the node's key of the engine's state packages beside the controller's mirror's key and their
|
||||
// difference, and with write writes the key again from what the engine composed (the verb packages, ADR 0565 §6, §15).
|
||||
Packages func(write bool) (any, error)
|
||||
// SeatList answers the seat list the engine holds; SeatListAgain makes it read the list now and answers what it holds.
|
||||
SeatList func() (any, error)
|
||||
SeatListAgain func() (any, error)
|
||||
// ComposeCandidate composes a check's candidate (the indexer's candidate key) on this node in shadow and answers per
|
||||
// module, applying and writing nothing; with input, also the composition input it composed from (the verb
|
||||
// compose-candidate, ADR 0593 §7).
|
||||
@@ -219,6 +227,16 @@ func (h *HandActs) Call(c EngineCall) (any, error) {
|
||||
return nil, errors.New("this engine composes nothing on a call yet: its node agent has not started")
|
||||
}
|
||||
return h.Compose(arg("module"))
|
||||
case ActSeatList:
|
||||
if h.SeatList == nil {
|
||||
return nil, errors.New("this engine reads no seat list yet: its node agent has not started")
|
||||
}
|
||||
return h.SeatList()
|
||||
case ActSeatListAgain:
|
||||
if h.SeatListAgain == nil {
|
||||
return nil, errors.New("this engine reads no seat list yet: its node agent has not started")
|
||||
}
|
||||
return h.SeatListAgain()
|
||||
case ActPackages:
|
||||
if h.Packages == nil {
|
||||
return nil, errors.New("this engine keeps no packages yet: its node agent has not started")
|
||||
|
||||
@@ -181,7 +181,12 @@ func CalledVaultSubject(verb, node string) string {
|
||||
// CalledVaultSubjectAs is CalledVaultSubject for the login the engine is dialled as (node.<n> or node.<n>--b, ADR 0583 §1):
|
||||
// the bus lets only that user publish a subject naming it.
|
||||
func CalledVaultSubjectAs(verb, user string) string {
|
||||
return "mesh.seat." + VaultSeat + ".call." + verb + "." + strings.ReplaceAll(user, ".", "~")
|
||||
return CalledSeatSubjectAs(VaultSeat, verb, user)
|
||||
}
|
||||
|
||||
// CalledSeatSubjectAs is where a verb of a seat held once for the mesh is asked naming the login the engine is dialled as.
|
||||
func CalledSeatSubjectAs(seat, verb, user string) string {
|
||||
return "mesh.seat." + seat + ".call." + verb + "." + strings.ReplaceAll(user, ".", "~")
|
||||
}
|
||||
|
||||
// VaultWithin is how long the engine gives the vault to answer one step.
|
||||
@@ -190,6 +195,17 @@ const VaultWithin = 15 * time.Second
|
||||
// AskVault asks one of the vault's verbs in this node's engine's own name over the link open now, and answers its
|
||||
// result, or its refusal as an error.
|
||||
func (q *Session) AskVault(ctx context.Context, verb string, args any) (json.RawMessage, error) {
|
||||
return q.askCalled(ctx, VaultSeat, verb, args, VaultWithin, "the vault's "+verb)
|
||||
}
|
||||
|
||||
// AskMeshSeat asks a verb of a seat held once for the mesh in this node's engine's own name over the link open now, and
|
||||
// answers its result, or its refusal as an error: the way the engine reads the seat list from the seat mesh-catalog
|
||||
// (novox/hq ADR 0636 §4).
|
||||
func (q *Session) AskMeshSeat(ctx context.Context, seat, verb string, args any) (json.RawMessage, error) {
|
||||
return q.askCalled(ctx, seat, verb, args, VaultWithin, seat+"."+verb)
|
||||
}
|
||||
|
||||
func (q *Session) askCalled(ctx context.Context, seat, verb string, args any, within time.Duration, what string) (json.RawMessage, error) {
|
||||
q.init()
|
||||
q.mu.Lock()
|
||||
bus := q.bus
|
||||
@@ -205,18 +221,18 @@ func (q *Session) AskVault(ctx context.Context, verb string, args any) (json.Raw
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
asking, cancel := context.WithTimeout(ctx, VaultWithin)
|
||||
asking, cancel := context.WithTimeout(ctx, within)
|
||||
defer cancel()
|
||||
reply, err := asker.Ask(asking, CalledVaultSubjectAs(verb, q.Membership.User()), body)
|
||||
reply, err := asker.Ask(asking, CalledSeatSubjectAs(seat, verb, q.Membership.User()), body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the vault's %s could not be asked: %w", verb, err)
|
||||
return nil, fmt.Errorf("%s could not be asked: %w", what, err)
|
||||
}
|
||||
var env struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(reply, &env); err != nil {
|
||||
return nil, fmt.Errorf("the vault answered %s with something that is not a tool's answer", verb)
|
||||
return nil, fmt.Errorf("%s answered with something that is not a tool's answer", what)
|
||||
}
|
||||
if env.Error != "" {
|
||||
return nil, errors.New(env.Error)
|
||||
|
||||
+6
-1
@@ -67,6 +67,7 @@
|
||||
"$JS.API.STREAM.INFO.KV_mesh-delivery-indexer_verdicts",
|
||||
"$JS.API.STREAM.INFO.KV_mesh-host_acts",
|
||||
"$JS.API.STREAM.INFO.KV_mesh-host_data",
|
||||
"$JS.API.STREAM.INFO.KV_mesh-host_findings",
|
||||
"$JS.API.STREAM.INFO.KV_mesh-host_health",
|
||||
"$JS.API.STREAM.INFO.KV_mesh-host_packages",
|
||||
"$JS.API.STREAM.INFO.KV_mesh-provisioner_provisions",
|
||||
@@ -91,6 +92,7 @@
|
||||
"$KV.composed-NODE.>",
|
||||
"$KV.mesh-host_acts.NODE.>",
|
||||
"$KV.mesh-host_data.NODE.>",
|
||||
"$KV.mesh-host_findings.>",
|
||||
"$KV.mesh-host_health.NODE",
|
||||
"$KV.mesh-host_packages.NODE",
|
||||
"$KV.node-engine_have.NODE.>",
|
||||
@@ -100,6 +102,7 @@
|
||||
"$SRV.PING.>",
|
||||
"$SRV.PING.node-tools.NODE",
|
||||
"mesh.control.NODE.>",
|
||||
"mesh.seat.mesh-catalog.call.seat-definitions.node~NODE",
|
||||
"mesh.seat.node-backup.call.backed-up.NODE.node~NODE",
|
||||
"mesh.seat.node-backup.call.now.NODE.node~NODE",
|
||||
"mesh.seat.node-backup.tool.backed-up.NODE",
|
||||
@@ -130,8 +133,10 @@
|
||||
"mesh.node.NODE.ask.report",
|
||||
"mesh.node.NODE.ask.setuid-search",
|
||||
"mesh.node.NODE.declare",
|
||||
"mesh.seat.mesh-catalog.event.seat-definition-changed",
|
||||
"mesh.seat.mesh-catalog.event.seat-definition-removed",
|
||||
"mesh.seat.node-engine.call.*.NODE.*",
|
||||
"mesh.seat.node-engine.tool.*.NODE"
|
||||
],
|
||||
"AllowResponses": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user