Archived
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e9a38f81b | ||
|
|
be8a9fd00f | ||
|
|
161e676b97 |
@@ -281,7 +281,12 @@ var defaultSeats = append([]Seat{
|
||||
// channel a fix could arrive on. A seat
|
||||
// rather than a condition in the resolver's module, so a machine running two managers is
|
||||
// refused at assignment instead of found by the resolver being rewritten (novox/hq ADR 0117).
|
||||
{Name: "node-uplink", Scope: ScopeNode, Decision: "novox/hq ADR 0117"},
|
||||
//
|
||||
// Its verbs say what the machine resolves through and over which links (novox/hq ADR 0241): the
|
||||
// resolver file as it is and who wrote it, and every link with its addresses, routes and the resolvers
|
||||
// the manager knows for it — the same for every holder, so a question about a machine's names is asked
|
||||
// the same way whatever manages its network. Optional until both holders serve them.
|
||||
{Name: "node-uplink", Scope: ScopeNode, Decision: "novox/hq ADR 0117", Serves: uplinkVerbs()},
|
||||
},
|
||||
// The graphical session's roles (novox/hq ADR 0208), last because they are a workstation's.
|
||||
graphicalSessionSeats()...)
|
||||
@@ -338,6 +343,26 @@ func UseSeats(s []Seat) {
|
||||
if d, known := byName[row.Name]; known {
|
||||
row.Receives = d.Receives
|
||||
row.Replicated = d.Replicated
|
||||
// Optional verbs are never in the row (Verb.Optional): they come from the binary that promises
|
||||
// them, still optional; and a row kept from before the mark keeps none of its own required.
|
||||
optional := map[string]Verb{}
|
||||
for _, v := range d.Serves {
|
||||
if v.Optional {
|
||||
optional[v.Name] = v
|
||||
}
|
||||
}
|
||||
have := map[string]bool{}
|
||||
for i, v := range row.Serves {
|
||||
have[v.Name] = true
|
||||
if o, ok := optional[v.Name]; ok {
|
||||
row.Serves[i] = o
|
||||
}
|
||||
}
|
||||
for _, v := range d.Serves {
|
||||
if v.Optional && !have[v.Name] {
|
||||
row.Serves = append(row.Serves, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
merged = append(merged, row)
|
||||
}
|
||||
@@ -591,6 +616,34 @@ func serviceManagerVerbs() []Verb {
|
||||
}
|
||||
}
|
||||
|
||||
// Stored is the seat as its row keeps it: its optional verbs left out (Verb.Optional).
|
||||
func (s Seat) Stored() Seat {
|
||||
var kept []Verb
|
||||
for _, v := range s.Serves {
|
||||
if !v.Optional {
|
||||
kept = append(kept, v)
|
||||
}
|
||||
}
|
||||
s.Serves = kept
|
||||
return s
|
||||
}
|
||||
|
||||
// uplinkVerbs is the contract every holder of node-uplink serves (novox/hq ADR 0241): read-only answers
|
||||
// about what the machine resolves through and over which links, whatever program manages its network.
|
||||
func uplinkVerbs() []Verb {
|
||||
return []Verb{
|
||||
{Name: "resolvers", Description: "The machine's resolver file as it is now: the resolvers, search " +
|
||||
"domains and options it lists, whether it is the file the mesh declares, and when it is not, who " +
|
||||
"wrote it as far as the machine shows — its header, a backup a VPN client left beside it, a link " +
|
||||
"in its place, a known writer running.",
|
||||
Input: schema(nil, nil), Optional: true},
|
||||
{Name: "links", Description: "Every network link on the machine: its state, its addresses, whether " +
|
||||
"the default route leaves through it, and the resolvers and search domains the network manager " +
|
||||
"knows for it — a VPN's tunnel included.",
|
||||
Input: schema(nil, nil), Optional: true},
|
||||
}
|
||||
}
|
||||
|
||||
// loginShellVerbs is the contract every holder of node-login-shell serves (novox/hq ADR 0176, ADR
|
||||
// 0204): one command, run the way the operator's own terminal would run it, bounded below the
|
||||
// runtime's thirty-second call limit so a hung command answers rather than times the caller out.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The node-uplink seat's verbs (novox/hq ADR 0241): promised and routed, optional — not yet a condition of
|
||||
// holding — so the holders of today still hold it, a holder may name them, and nothing it does not promise.
|
||||
// And an optional verb is never stored in the seat's row (Verb.Optional): a controller that reads the row
|
||||
// back must still find it optional, and one older than the verb must not find it at all.
|
||||
|
||||
func uplinkSeat(t *testing.T) Seat {
|
||||
t.Helper()
|
||||
for _, s := range DefaultSeats() {
|
||||
if s.Name == "node-uplink" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
t.Fatal("no node-uplink seat")
|
||||
return Seat{}
|
||||
}
|
||||
|
||||
func TestTheUplinkSeatPromisesItsVerbsAndRequiresNoneYet(t *testing.T) {
|
||||
seat := uplinkSeat(t)
|
||||
var names []string
|
||||
for _, v := range seat.Serves {
|
||||
names = append(names, v.Name)
|
||||
if !v.Optional {
|
||||
t.Errorf("%s is required already", v.Name)
|
||||
}
|
||||
}
|
||||
if !slices.Equal(names, []string{"resolvers", "links"}) {
|
||||
t.Fatalf("node-uplink promises %v", names)
|
||||
}
|
||||
today := Manifest{Module: "networkmanager", Version: "1", Claims: []Claim{{Name: "node-uplink", Scope: ScopeNode}}}
|
||||
if err := CanHold(today, seat); err != nil {
|
||||
t.Fatalf("a holder that serves no verb yet is refused: %v", err)
|
||||
}
|
||||
serving := today
|
||||
serving.Claims = []Claim{{Name: "node-uplink", Scope: ScopeNode, Serves: []string{"resolvers", "links"}}}
|
||||
if err := CanHold(serving, seat); err != nil {
|
||||
t.Fatalf("a holder serving both is refused: %v", err)
|
||||
}
|
||||
typo := today
|
||||
typo.Claims = []Claim{{Name: "node-uplink", Scope: ScopeNode, Serves: []string{"resolver"}}}
|
||||
if err := CanHold(typo, seat); err == nil || !strings.Contains(err.Error(), "does not promise") {
|
||||
t.Fatalf("a verb the seat does not promise was accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnOptionalVerbIsNeverStoredAndComesBackOptionalFromTheBinary(t *testing.T) {
|
||||
seat := uplinkSeat(t)
|
||||
if stored := seat.Stored(); len(stored.Serves) != 0 {
|
||||
t.Fatalf("the row would keep %v", stored.Serves)
|
||||
}
|
||||
before := seats
|
||||
t.Cleanup(func() { seats = before })
|
||||
optionalBoth := func(want int) {
|
||||
t.Helper()
|
||||
for _, s := range Seats() {
|
||||
if s.Name != "node-uplink" {
|
||||
continue
|
||||
}
|
||||
if len(s.Serves) != want {
|
||||
t.Fatalf("the working set's node-uplink serves %+v", s.Serves)
|
||||
}
|
||||
holder := Manifest{Module: "networkmanager", Version: "1", Claims: []Claim{{Name: "node-uplink", Scope: ScopeNode}}}
|
||||
for _, v := range s.Serves {
|
||||
if v.Name != "operators-own" {
|
||||
if !v.Optional {
|
||||
t.Fatalf("%s came back required", v.Name)
|
||||
}
|
||||
} else {
|
||||
holder.Claims[0].Serves = []string{"operators-own"}
|
||||
}
|
||||
}
|
||||
if err := CanHold(holder, s); err != nil {
|
||||
t.Fatalf("a holder serving none of the optional verbs is refused: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// A row as the store keeps it: no protocol of its own.
|
||||
UseSeats([]Seat{{Name: "node-uplink", Scope: ScopeNode}, {Name: "node-service-manager", Scope: ScopeNode}})
|
||||
optionalBoth(2)
|
||||
// A row with a verb an operator added: it keeps it and gains the optional ones.
|
||||
UseSeats([]Seat{{Name: "node-uplink", Scope: ScopeNode, Serves: []Verb{{Name: "operators-own"}}}})
|
||||
optionalBoth(3)
|
||||
// A row that stored the verb before it was marked (or by an older seeding): read back, still optional.
|
||||
UseSeats([]Seat{{Name: "node-uplink", Scope: ScopeNode, Serves: []Verb{{Name: "resolvers"}, {Name: "links"}}}})
|
||||
optionalBoth(2)
|
||||
}
|
||||
@@ -28,7 +28,9 @@ type Verb struct {
|
||||
// does not serve the verb, the controller before it refusing the holder that does — and every check of
|
||||
// the catalogue between the two would fail on a module nobody touched. Once every holder serves it,
|
||||
// the mark is removed and the verb is a condition of holding like the rest. Never stored or said: the
|
||||
// seat set's protocol is the compiled one.
|
||||
// seat's row leaves an optional verb out (Seat.Stored) and the working set takes it, still optional,
|
||||
// from the compiled seat (UseSeats) — the mark itself is not a column, so a stored verb would read back
|
||||
// required, and a controller older than the verb would require it of every holder (novox/hq ADR 0241).
|
||||
Optional bool `json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ func (i *Inventory) Seats(ctx context.Context) ([]catalogue.Seat, error) {
|
||||
func (i *Inventory) SeedSeats(ctx context.Context, defaults []catalogue.Seat) (int, error) {
|
||||
var added int
|
||||
for _, s := range defaults {
|
||||
// An optional verb is never stored (catalogue.Verb.Optional).
|
||||
s = s.Stored()
|
||||
accepts, emits, serves, err := protocolJSON(s)
|
||||
if err != nil {
|
||||
return added, err
|
||||
|
||||
@@ -156,3 +156,28 @@ func TestTheControllersVerbsInTheRowAreTheBinarys(t *testing.T) {
|
||||
t.Fatal("a verb this binary adds was not added")
|
||||
}
|
||||
}
|
||||
|
||||
// An optional verb (catalogue.Verb.Optional; novox/hq ADR 0241, ADR 0239) is never in the store's row: a
|
||||
// controller older than it reads the row and would require what no holder of its time serves, and the mark
|
||||
// itself is not stored, so the row would read back required.
|
||||
func TestAnOptionalVerbIsNotSeeded(t *testing.T) {
|
||||
inv := ForTest(t)
|
||||
if _, err := inv.SeedSeats(t.Context(), catalogue.DefaultSeats()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Seeded twice: the second widens, and must not add it either.
|
||||
if _, err := inv.SeedSeats(t.Context(), catalogue.DefaultSeats()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := inv.Seats(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, s := range got {
|
||||
for _, v := range s.Serves {
|
||||
if (s.Name == "node-uplink") || (s.Name == catalogue.DeliverySeat && v.Name == "checks") {
|
||||
t.Fatalf("%s's row keeps the optional %s", s.Name, v.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user