stage 1 of 8 for issue 1389 (a staging branch; the tree is copied by one commit)
This commit is contained in:
@@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
@@ -708,18 +707,10 @@ func converge(ctx context.Context, open *stores, node string, yes bool, digest s
|
||||
}
|
||||
|
||||
// What the filter is derived from: the rendering of this machine as a question reads it, with the private network.
|
||||
input, err := inputFor(ctx, open)
|
||||
with, err := renderingOf(ctx, open, node)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rendered, err := compose.Render(input.Input, node, compose.Reading)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
with := rendered.Rendering
|
||||
if with.Generators, err = compose.Generators(input.Input); err != nil {
|
||||
return "", err
|
||||
}
|
||||
rules, err := plan.Rules(with)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
"git.novox.be/novox/mesh-sdk/go/overlay"
|
||||
"github.com/novox/mesh-controller/internal/conditions"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
@@ -25,21 +26,13 @@ func TestAModuleAssignedAndNotPushedIsAwaitingAPushNotUncomposable(t *testing.T)
|
||||
|
||||
// The read the rest of the mesh asks still refuses it, with the composer's typed error: nothing
|
||||
// can be compared about a secret that does not exist (status), and nothing here string-matches.
|
||||
plan, settings, err := planFor(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gens, err := generators(ctx, open)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = declarationWith(ctx, open, "laptop", plan, settings, gens, Reading)
|
||||
_, _, err := composeQuestion(ctx, open, "laptop", compose.Reading, false)
|
||||
var notMade *catalogue.NotMadeError
|
||||
if !errors.As(err, ¬Made) || notMade.Module != "keeper" || notMade.Name != "repository" {
|
||||
t.Fatalf("a read composition did not say which secret is not made, typed: %v", err)
|
||||
}
|
||||
// Foreseen, it composes, names what the push makes, and made nothing.
|
||||
foreseen, err := declarationWith(ctx, open, "laptop", plan, settings, gens, Foreseeing)
|
||||
foreseen, _, err := composeQuestion(ctx, open, "laptop", compose.Foreseeing, false)
|
||||
if err != nil || len(foreseen.foreseen) != 1 || foreseen.foreseen[0] != "keeper/repository" {
|
||||
t.Fatalf("foreseen: %v %v", foreseen.foreseen, err)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func aKeeper() catalogue.Manifest {
|
||||
func pushedBy(t *testing.T, open *stores, node string) string {
|
||||
t.Helper()
|
||||
ctx := t.Context()
|
||||
plan, settings, err := planFor(ctx, open, node)
|
||||
plan, settings, err := resolveNode(ctx, open, node)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -54,11 +54,10 @@ func pushedBy(t *testing.T, open *stores, node string) string {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
names := make([]string, 0, len(plan.Modules))
|
||||
builds = map[string]string{}
|
||||
for _, m := range plan.Modules {
|
||||
names = append(names, m.Module)
|
||||
builds[m.Module] = current[m.Module].Commit
|
||||
}
|
||||
builds = carriedBuilds(names, nil, current, nil)
|
||||
}
|
||||
if err := open.inventory.RecordSent(ctx, record.ID, digest, builds); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestTheIncidentAConsumerStaysBesideItsDataUntilAPersonPinsIt(t *testing.T)
|
||||
assignAll([2]string{"laptop", "store"}, [2]string{"laptop", "resolver"}, [2]string{"laptop", "network"},
|
||||
[2]string{"laptop", "board"})
|
||||
|
||||
plan, _, err := planFor(ctx, open, "laptop")
|
||||
plan, _, err := resolveNode(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func TestTheIncidentAConsumerStaysBesideItsDataUntilAPersonPinsIt(t *testing.T)
|
||||
if err := inv.Unassign(ctx, "laptop", "store"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := planFor(ctx, open, "laptop"); err == nil || !unresolvable(err) ||
|
||||
if _, _, err := resolveNode(ctx, open, "laptop"); err == nil || !unresolvable(err) ||
|
||||
!strings.Contains(err.Error(), "board on laptop is bound to laptop/store") ||
|
||||
!strings.Contains(err.Error(), "pin laptop postgres-database anchor store") {
|
||||
t.Fatalf("the consumer's store went and it was answered elsewhere: %v", err)
|
||||
@@ -74,7 +74,7 @@ func TestTheIncidentAConsumerStaysBesideItsDataUntilAPersonPinsIt(t *testing.T)
|
||||
if err := inv.PinProvision(ctx, "laptop", "postgres-database", "anchor", "store"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plan, _, err = planFor(ctx, open, "laptop")
|
||||
plan, _, err = resolveNode(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -10,21 +10,13 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 1388: no caller composes through the controller's own composition any more. The only functions left that call it are the
|
||||
// composition's own, which issue 1389 deletes with it; this fails the moment any other function reaches for it again.
|
||||
func TestNoCallerComposesThroughTheControllersOwnComposition(t *testing.T) {
|
||||
legacy := map[string]bool{"planFor": true, "declarationWith": true, "composedAndValidated": true, "generators": true,
|
||||
"renderingFor": true, "theRestOfTheMesh": true, "whoResolves": true}
|
||||
// The composition's own pairs, until 1389: "file: caller calls callee".
|
||||
allowed := map[string]bool{
|
||||
"facts.go: composedAndValidated calls declarationWith": true,
|
||||
"facts.go: composedAndValidated calls planFor": true,
|
||||
"network.go: generators calls whoResolves": true,
|
||||
"network.go: whoResolves calls planFor": true,
|
||||
"plan.go: declarationWith calls renderingFor": true,
|
||||
"plan.go: grantsFor calls planFor": true,
|
||||
"plan.go: planFor calls theRestOfTheMesh": true,
|
||||
}
|
||||
// 1389: the controller's own composition is gone. No function of the package is named for it any more, and no function
|
||||
// calls one by that name: a name reappearing is a second copy of what mesh-sdk's go/compose holds (novox/hq ADR 0453).
|
||||
func TestTheControllersOwnCompositionIsGone(t *testing.T) {
|
||||
gone := map[string]bool{"planFor": true, "declarationWith": true, "composedAndValidated": true, "generators": true,
|
||||
"renderingFor": true, "theRestOfTheMesh": true, "whoResolves": true, "grantsFor": true, "namesInTheMesh": true,
|
||||
"onThePrivateNetwork": true, "passSeatsOn": true, "theMeshPassesSeatsOn": true, "assignmentsOf": true,
|
||||
"licencesFor": true, "seatsOn": true, "composeBusUsers": true, "adoptionFor": true, "localNetworksOf": true}
|
||||
files, err := filepath.Glob("*.go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -41,12 +33,18 @@ func TestNoCallerComposesThroughTheControllersOwnComposition(t *testing.T) {
|
||||
}
|
||||
for _, d := range file.Decls {
|
||||
fn, ok := d.(*ast.FuncDecl)
|
||||
if !ok || fn.Body == nil {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if gone[fn.Name.Name] {
|
||||
found = append(found, f+": "+fn.Name.Name+" is declared")
|
||||
}
|
||||
if fn.Body == nil {
|
||||
continue
|
||||
}
|
||||
ast.Inspect(fn.Body, func(n ast.Node) bool {
|
||||
if c, ok := n.(*ast.CallExpr); ok {
|
||||
if id, ok := c.Fun.(*ast.Ident); ok && legacy[id.Name] {
|
||||
if id, ok := c.Fun.(*ast.Ident); ok && gone[id.Name] {
|
||||
found = append(found, f+": "+fn.Name.Name+" calls "+id.Name)
|
||||
}
|
||||
}
|
||||
@@ -55,9 +53,7 @@ func TestNoCallerComposesThroughTheControllersOwnComposition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
sort.Strings(found)
|
||||
for _, pair := range found {
|
||||
if !allowed[pair] {
|
||||
t.Errorf("%s: it composes through the controller's own composition, not mesh-sdk's", pair)
|
||||
}
|
||||
for _, one := range found {
|
||||
t.Errorf("%s: the controller's own composition is back; compose through mesh-sdk (compose_sdk.go)", one)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
)
|
||||
|
||||
// grantsOf is the grants a provider creates for its consumers, the consumers whose identity overflows the provision's bound
|
||||
// and the credentials bound to another consumer, as mesh-sdk's rendering gives them for the machine (it replaced the
|
||||
// controller's grantsFor, issue 1389).
|
||||
func grantsOf(ctx context.Context, open *stores, node string) ([]catalogue.Grant, []catalogue.Overflow, []catalogue.Unbound, error) {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
rendered, err := compose.Render(in.Input, node, compose.Reading)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
return rendered.Rendering.Grants, rendered.Withheld, rendered.Unbound, nil
|
||||
}
|
||||
|
||||
// madeAndRendered sends to the machine (so everything a send makes is made) and answers its rendering as a question reads it
|
||||
// afterwards: what the controller's Allocating rendering was.
|
||||
func madeAndRendered(ctx context.Context, open *stores, node string) (catalogue.Rendering, error) {
|
||||
if _, err := composeSend(ctx, open, node); err != nil {
|
||||
return catalogue.Rendering{}, err
|
||||
}
|
||||
forgetInput(ctx)
|
||||
return renderingOf(ctx, open, node)
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func TestAPairCredentialNoRowHoldsIsAWouldMakeAndNoRowIsInserted(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The controller's own planning makes the credentials of files and reports.
|
||||
if _, _, err := planFor(ctx, open, "laptop"); err != nil {
|
||||
if _, _, err := resolveNode(ctx, open, "laptop"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
register(t, open, aConsumerOfTheBucket("metrics"))
|
||||
|
||||
@@ -280,7 +280,7 @@ func TestAServedFactThatSaysASettingIsResolvedWithTheSettingInTheInput(t *testin
|
||||
if anchor, _ := nodeOf(in.Input, "anchor"); len(anchor.Settings["step-ca"]) == 0 {
|
||||
t.Fatalf("the Input does not carry the setting of the provider: %v", anchor.Settings)
|
||||
}
|
||||
oldPlan, _, err := planFor(ctx, open, "laptop")
|
||||
oldPlan, _, err := resolveNode(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -317,7 +317,7 @@ func TestAServedFactThatSaysAnUnsetSettingIsRefusedAsTheControllerRefusesIt(t *t
|
||||
if err := open.inventory.ClearSettings(ctx, "", "step-ca"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, oldErr := planFor(ctx, open, "laptop")
|
||||
_, _, oldErr := resolveNode(ctx, open, "laptop")
|
||||
_, newErr := readComposeInput(ctx, open)
|
||||
if (oldErr == nil) != (newErr == nil) {
|
||||
t.Fatalf("the controller's composition says %v, the adapter says %v", oldErr, newErr)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//go:build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// madeIn is what a store holds that a send makes, by key and never by value.
|
||||
func madeIn(ctx context.Context, open *stores) (made, error) {
|
||||
var out made
|
||||
inv := open.inventory
|
||||
nodes, err := inv.Nodes(ctx)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for _, n := range nodes {
|
||||
ports, err := inv.PortsFor(ctx, n.Name)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for _, p := range ports {
|
||||
out.Ports = append(out.Ports, fmt.Sprintf("%s %s:%d->%d", n.Name, p.Module, p.Wanted, p.Machine))
|
||||
}
|
||||
rows, err := inv.SecretsFrom(ctx, n.Name)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for _, s := range rows {
|
||||
out.Pairs = append(out.Pairs, fmt.Sprintf("%s %s on %s from %s local=%s", s.Name, s.ConsumerModule, s.Consumer, n.Name, s.Local))
|
||||
}
|
||||
}
|
||||
own, err := inv.OwnSecretRows(ctx)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for _, r := range own {
|
||||
out.OwnSecrets = append(out.OwnSecrets, fmt.Sprintf("%s %s/%s (%s)", r.Node, r.Module, r.Name, r.Origin))
|
||||
}
|
||||
sort.Strings(out.Ports)
|
||||
sort.Strings(out.Pairs)
|
||||
sort.Strings(out.OwnSecrets)
|
||||
return out, nil
|
||||
}
|
||||
@@ -46,26 +46,8 @@ func perform(ctx context.Context, open *stores, in *meshInput, node string, d co
|
||||
for _, line := range d.Passes.Said {
|
||||
fmt.Fprintln(w, line)
|
||||
}
|
||||
sp := seatPassingOn(inv, w)
|
||||
for _, p := range d.Passes.Passes {
|
||||
moved, err := inv.PassSeat(ctx, p.Seat.Name, p.Seat.Scope, p.From, p.To)
|
||||
if err != nil {
|
||||
// Said, and the record stays as it was: a composition is never refused for it.
|
||||
fmt.Fprintf(w, "%s could not be passed from %s on %s to %s on %s: %v\n", p.Seat.Name,
|
||||
p.From.Module, p.From.Node, p.To.Module, p.To.Node, err)
|
||||
continue
|
||||
}
|
||||
if !moved {
|
||||
continue // another composition passed it first, and wrote the act
|
||||
}
|
||||
if performSeatPasses(ctx, seatPassingOn(inv, w), d.Passes.Passes) {
|
||||
did = true
|
||||
if inv.Throwaway() {
|
||||
continue
|
||||
}
|
||||
if err := sp.act(ctx, p); err != nil {
|
||||
fmt.Fprintf(w, "%s passed from %s on %s to %s on %s, and the hand act could NOT be recorded: %v\n",
|
||||
p.Seat.Name, p.From.Module, p.From.Node, p.To.Module, p.To.Node, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +242,7 @@ func prepareSend(ctx context.Context, open *stores, node string) ([]string, erro
|
||||
if n.Stated && n.Contract >= compose.VaultBoxContract {
|
||||
boxes, err := coreBoxesFor(ctx, inv, node, resolved.Plan.Modules, func(m catalogue.Manifest) bool {
|
||||
return handing.Handing(m)
|
||||
}, Allocating)
|
||||
}, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestPerformAllocatesAForeseenPortAndTheNumberIsTheControllers(t *testing.T)
|
||||
t.Fatalf("a second composition names %v foreseen: %v", again.ForeseenPorts, err)
|
||||
}
|
||||
// The number is the one the controller allocates in the same store.
|
||||
plan, settings, err := planFor(ctx, open, "laptop")
|
||||
plan, settings, err := resolveNode(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -119,6 +119,24 @@ func generatorsOf(ctx context.Context, open *stores) (map[string]catalogue.Gener
|
||||
return compose.Generators(in.Input)
|
||||
}
|
||||
|
||||
// renderingOf is what a machine is rendered with as a question reads it (nothing made), with the private network: what the
|
||||
// filter's rules, the roster and the like are derived from.
|
||||
func renderingOf(ctx context.Context, open *stores, node string) (catalogue.Rendering, error) {
|
||||
in, err := inputFor(ctx, open)
|
||||
if err != nil {
|
||||
return catalogue.Rendering{}, err
|
||||
}
|
||||
rendered, err := compose.Render(in.Input, node, compose.Reading)
|
||||
if err != nil {
|
||||
return catalogue.Rendering{}, markUnresolvable(err)
|
||||
}
|
||||
with := rendered.Rendering
|
||||
if with.Generators, err = compose.Generators(in.Input); err != nil {
|
||||
return catalogue.Rendering{}, err
|
||||
}
|
||||
return with, nil
|
||||
}
|
||||
|
||||
// whoResolvesOn is the machines whose resolution answers a requirement, and why the others did not (whoResolves over the
|
||||
// Input): by what a module provides, not by its name. A machine whose set does not compose is in refused, said; one the mesh
|
||||
// could not be asked about is an error, never a refusal (novox/hq 04-ISSUES/152).
|
||||
|
||||
Reference in New Issue
Block a user