226 lines
7.6 KiB
Go
226 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
"github.com/novox/mesh-controller/internal/builderkeys"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// A new fingerprint of a registered module, from its done key to its compositions (novox/hq ADR 0539 clauses 5 and 7,
|
|
// ADR 0501 §2). With no delivery plan left to take a module's done key in, a write of the builder's or the indexer's
|
|
// keys does it: the controller takes in the done key of the fingerprint the indexer's trunk value names (registers the
|
|
// module at it, as a plan's take-in did), composes every node the module is assigned to (which makes what a module's
|
|
// new manifest needs, a credential among it, as a send made it), composes the node holding the bus (its user list now
|
|
// carries the module's permissions at the new fingerprint: a new revision of the bus module's composition alone,
|
|
// written into its want at once by the assigner), and writes `grants.<module>.<fp>`. The assigner's sixth and seventh
|
|
// rules then let a want name the fingerprint. **No timer**: woken by the keys, and a starting controller looks at every
|
|
// module once (B5's start pass).
|
|
|
|
// doneKeySteps are what taking a module's done key in does, each a step a test may stand in for.
|
|
type doneKeySteps struct {
|
|
// held says the catalogue holds the module (a new one is the new-module take-in's, new_modules.go).
|
|
held func(ctx context.Context, module string) (bool, error)
|
|
// trunk is the indexer's trunk value of the module.
|
|
trunk func(ctx context.Context, module string) (sdkdelivery.Module, error)
|
|
// registeredAt says the module is registered at the commit.
|
|
registeredAt func(ctx context.Context, module, commit string) (bool, error)
|
|
// takeIn registers the module from the builder's done key of the trunk's fingerprint (takeInKey); errKeyWaits when
|
|
// the key is not done yet.
|
|
takeIn func(ctx context.Context, module string, trunk sdkdelivery.Module) error
|
|
// nodesOf are the nodes the module is assigned to; busHolder the node holding the bus, "" for none.
|
|
nodesOf func(ctx context.Context, module string) ([]string, error)
|
|
busHolder func(ctx context.Context) (string, error)
|
|
// compose writes the nodes' compositions; grants writes the grants issued.
|
|
compose func(ctx context.Context, nodes []string) error
|
|
grants func(ctx context.Context) error
|
|
}
|
|
|
|
// take takes module's done key in and composes what it changed. It answers what it did in a line, "" when there was
|
|
// nothing to do.
|
|
func (s doneKeySteps) take(ctx context.Context, module string) (string, error) {
|
|
held, err := s.held(ctx, module)
|
|
if err != nil || !held {
|
|
return "", err
|
|
}
|
|
trunk, err := s.trunk(ctx, module)
|
|
if errors.Is(err, builderkeys.ErrNoValue) {
|
|
return "", nil
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !strings.HasPrefix(trunk.FP2, sdkdelivery.SourcePrefix2) {
|
|
return "", nil
|
|
}
|
|
at, err := s.registeredAt(ctx, module, trunk.Commit)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
said := ""
|
|
if !at {
|
|
if err := s.takeIn(ctx, module, trunk); err != nil {
|
|
return "", err
|
|
}
|
|
said = fmt.Sprintf("%s registered from the builder's done key %s (%s)", module, short(trunk.FP2), short(trunk.Commit))
|
|
}
|
|
nodes, err := s.nodesOf(ctx, module)
|
|
if err != nil {
|
|
return said, err
|
|
}
|
|
if len(nodes) == 0 {
|
|
return said, nil
|
|
}
|
|
// The module's nodes first: composing them makes what the new manifest needs, its credentials among it.
|
|
if err := s.compose(ctx, nodes); err != nil {
|
|
return said, err
|
|
}
|
|
// Then the bus's user list, which carries the module's permissions at its new fingerprint.
|
|
holder, err := s.busHolder(ctx)
|
|
if err != nil {
|
|
return said, err
|
|
}
|
|
if holder != "" && !inList(nodes, holder) {
|
|
if err := s.compose(ctx, []string{holder}); err != nil {
|
|
return said, err
|
|
}
|
|
}
|
|
// Then the grant, which with the composition lets a want name the fingerprint (rules 6 and 7).
|
|
if err := s.grants(ctx); err != nil {
|
|
return said, err
|
|
}
|
|
if said == "" {
|
|
said = fmt.Sprintf("%s at %s", module, short(trunk.FP2))
|
|
}
|
|
return said + "; composed for " + strings.Join(nodes, ", ") + ", its grant written", nil
|
|
}
|
|
|
|
func inList(list []string, s string) bool {
|
|
for _, v := range list {
|
|
if v == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// doneKeysMu keeps one take-in of a module's done key at a time in this process, per module.
|
|
var doneKeysMu sync.Map
|
|
|
|
// takeDoneKey is a woken module's take-in on the live stores and buckets, said.
|
|
func takeDoneKey(ctx context.Context, open *stores, module string) {
|
|
mu, _ := doneKeysMu.LoadOrStore(module, &sync.Mutex{})
|
|
mu.(*sync.Mutex).Lock()
|
|
defer mu.(*sync.Mutex).Unlock()
|
|
keys, closeKeys, err := openBuilderKeys()
|
|
if err != nil {
|
|
fmt.Printf("done keys: %s: the builder's keys cannot be read: %v\n", module, err)
|
|
return
|
|
}
|
|
defer closeKeys()
|
|
said, err := liveDoneKeySteps(open, keys).take(ctx, module)
|
|
switch {
|
|
case errors.Is(err, errKeyWaits):
|
|
// Said by nobody: the key's next write wakes this again.
|
|
case err != nil:
|
|
fmt.Printf("done keys: %s: %v\n", module, err)
|
|
case said != "":
|
|
fmt.Printf("done keys: %s\n", said)
|
|
}
|
|
}
|
|
|
|
// liveDoneKeySteps are the steps on the controller's own stores and the bus.
|
|
func liveDoneKeySteps(open *stores, keys registerKeys) doneKeySteps {
|
|
inv := open.inventory
|
|
return doneKeySteps{
|
|
held: func(ctx context.Context, module string) (bool, error) {
|
|
_, err := inv.SourceOf(ctx, module)
|
|
if errors.Is(err, inventory.ErrNoSuchModule) {
|
|
return false, nil
|
|
}
|
|
return err == nil, err
|
|
},
|
|
trunk: func(ctx context.Context, module string) (sdkdelivery.Module, error) {
|
|
return keys.ModuleAt(ctx, module, "")
|
|
},
|
|
registeredAt: func(ctx context.Context, module, commit string) (bool, error) {
|
|
src, err := inv.SourceOf(ctx, module)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return sameCommitID(src.BuiltFrom, commit), nil
|
|
},
|
|
takeIn: func(ctx context.Context, module string, trunk sdkdelivery.Module) error {
|
|
_, _, err := takeInKey(ctx, inv, keys, keyTake{Module: module, Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}})
|
|
if errors.Is(err, inventory.ErrSuperseded) {
|
|
return nil
|
|
}
|
|
return err
|
|
},
|
|
nodesOf: func(ctx context.Context, module string) ([]string, error) {
|
|
entries, err := inv.Catalogued(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, e := range entries {
|
|
if e.Manifest.Module == module {
|
|
return e.On, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
},
|
|
busHolder: func(ctx context.Context) (string, error) {
|
|
holders, err := seatHolders(ctx, inv)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return holders[catalogue.BrokerSeat].Node, nil
|
|
},
|
|
compose: func(ctx context.Context, nodes []string) error {
|
|
_, err := composeModules(ctx, open, nodes)
|
|
return err
|
|
},
|
|
grants: func(ctx context.Context) error {
|
|
bus, err := aBus()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer bus.Close()
|
|
js, err := jetstream.New(bus.Conn())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
grantsIssued(ctx, inv, js, "done keys: ")
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
// watchDoneKeys wakes the take-in of a module's done key for each module the builder or the indexer writes, until ctx
|
|
// ends: never on the watch's own goroutine.
|
|
func watchDoneKeys(ctx context.Context, open *stores) {
|
|
keys, closeKeys, err := openBuilderKeys()
|
|
if err != nil {
|
|
fmt.Printf("done keys: the builder's keys are not watched: %v\n", err)
|
|
return
|
|
}
|
|
if err := builderkeys.Watch(ctx, keys.JS, func(module string) {
|
|
inBackground(func() { takeDoneKey(ctx, open, module) })
|
|
}); err != nil {
|
|
closeKeys()
|
|
fmt.Printf("done keys: the builder's keys are not watched: %v\n", err)
|
|
return
|
|
}
|
|
go func() {
|
|
<-ctx.Done()
|
|
closeKeys()
|
|
}()
|
|
}
|