A composition is applied only when the controller or the operator signed it; the node agent's integration tests on an embedded bus (R6-A A9, A1, A5)

This commit is contained in:
2026-10-24 16:42:11 +02:00
parent 3303797bfb
commit b2003e7a38
15 changed files with 515 additions and 58 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ func TestNatsAToolCheckIsAskedOnTheLink(t *testing.T) {
}
q := &Session{Membership: Membership{Node: node}}
q.attach(t.Context(), &natsLink{conn: conn, js: js, node: node})
q.Attach(t.Context(), &natsLink{conn: conn, js: js, node: node})
ctx, cancel := context.WithTimeout(t.Context(), 2*time.Second)
defer cancel()
a, err := q.AskTool(ctx, "sensors", "sensors_health")
+1 -1
View File
@@ -43,7 +43,7 @@ func TestNatsHavesAreWrittenOnceAndOnlyOnChange(t *testing.T) {
Windows: []Window{{Step: "backup", Holds: []string{"db"}}}}
}
q := &Session{Membership: m, Timeout: 2 * time.Second}
q.attach(context.Background(), OverNATS{Conn: conn, JS: js})
q.Attach(context.Background(), OverNATS{Conn: conn, JS: js})
ctx := context.Background()
revisions := func() map[string]uint64 {
+2 -2
View File
@@ -139,7 +139,7 @@ func aHaveQueue(l *haveLink) (*Session, *[]string) {
said = append(said, s)
mu.Unlock()
}}
q.attach(context.Background(), l)
q.Attach(context.Background(), l)
return q, &said
}
@@ -277,7 +277,7 @@ func TestAFailedHaveWriteIsSaidAndNeverBlocksTheReport(t *testing.T) {
func TestNoBucketAndNoFactsWriteNothing(t *testing.T) {
plain := newQuietLink()
q := &Session{Membership: Membership{Node: "n1"}, Timeout: time.Second}
q.attach(context.Background(), plain)
q.Attach(context.Background(), plain)
if !q.SayFacts(context.Background(), appliedModules()) {
t.Fatal("the report did not go out")
}
+6 -6
View File
@@ -30,7 +30,7 @@ func TestAHealthStatementIsSaidOnTheLinkOpenNow(t *testing.T) {
t.Fatal("said with no link open")
}
l := &sayingLink{quietLink: newQuietLink()}
q.attach(t.Context(), l)
q.Attach(t.Context(), l)
if !q.SayHealth(t.Context(), h) || len(l.said) != 1 {
t.Fatalf("not said on the open link: %d", len(l.said))
}
@@ -42,8 +42,8 @@ func TestAHealthStatementIsSaidOnTheLinkOpenNow(t *testing.T) {
t.Fatalf("said %+v", got)
}
// A link that cannot say it is not an error: the next report carries the statement.
q.detach(l)
q.attach(t.Context(), newQuietLink())
q.Detach(l)
q.Attach(t.Context(), newQuietLink())
if q.SayHealth(t.Context(), h) {
t.Fatal("said on a link that cannot")
}
@@ -83,16 +83,16 @@ func TestAToolCheckIsAskedOnTheLinkTheEngineHolds(t *testing.T) {
}
quiet := newQuietLink()
q.attach(t.Context(), quiet)
q.Attach(t.Context(), quiet)
if _, err := q.AskTool(t.Context(), "sensors", "sensors_health"); err == nil || err.Error() != "the link to the bus open now cannot ask a tool" {
t.Fatalf("on a link that cannot ask: %v", err)
}
q.detach(quiet)
q.Detach(quiet)
// The link Open returns, with no connection under it: the question must reach its Ask (and fail
// there), never stop at the queue's assertion.
held := &natsLink{node: "anchor"}
q.attach(t.Context(), held)
q.Attach(t.Context(), held)
_, err := q.AskTool(t.Context(), "sensors", "sensors_health")
if err == nil {
t.Fatal("asked on no connection and was answered")
+1 -1
View File
@@ -113,7 +113,7 @@ func TestNatsAMoveIsAskedOfTheNodeAndTheVaultIsAskedInItsName(t *testing.T) {
defer vault.Unsubscribe()
l := &natsLink{conn: conn, js: js, node: m.Node, signer: m.Signer}
q := &Session{Membership: m}
q.attach(t.Context(), l)
q.Attach(t.Context(), l)
l.HearMoveSecrets(func(s MoveSecret) (MovedSecret, error) {
raw, err := q.AskVault(t.Context(), "move-in", map[string]string{"module": s.Module})
if err != nil || !strings.Contains(string(raw), `"call":"c1"`) {
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// link and fail there, not stop at the queue as if no link were open.
func TestReplay331(t *testing.T) {
q := &Session{Membership: Membership{Node: "laptop"}}
q.attach(t.Context(), &natsLink{node: "laptop"})
q.Attach(t.Context(), &natsLink{node: "laptop"})
_, err := q.AskTool(t.Context(), "sensors", "sensors_health")
if err == nil {
t.Fatal("asked on no connection and was answered")
+2 -2
View File
@@ -175,8 +175,8 @@ func serve(ctx context.Context, link Link, m Membership, queue *Session, say Ann
say("in the mesh, hearing what the controller asks of this node")
// The session says this node's facts as the link is handed to it, and is not let go while it is mid-act.
queue.attach(ctx, link)
defer queue.detach(link)
queue.Attach(ctx, link)
defer queue.Detach(link)
// And the mesh asking this node to say its facts again, on a link that hears the question.
var asks <-chan struct{}
+4 -4
View File
@@ -116,8 +116,8 @@ func (q *Session) FactsAsked(ctx context.Context) {
}
}
// attach is a link opening: it is the link facts are said on from now.
func (q *Session) attach(ctx context.Context, bus Bus) {
// Attach is a link opening: it is the link facts are said on from now.
func (q *Session) Attach(ctx context.Context, bus Bus) {
q.init()
q.mu.Lock()
q.bus = bus
@@ -140,8 +140,8 @@ func (q *Session) WithLink(ctx context.Context) context.Context {
return WithFiles(ctx, bus)
}
// detach is the link closing: nothing more is said on it.
func (q *Session) detach(bus Bus) {
// Detach is the link closing: nothing more is said on it.
func (q *Session) Detach(bus Bus) {
q.init()
q.saying.Lock()
defer q.saying.Unlock()
+5 -5
View File
@@ -64,7 +64,7 @@ func TestTheFactsEventCarriesTheNodeEnginesWriterEpochAndSequence(t *testing.T)
m, _ := aMember(t)
l := newQuietLink()
s := &Session{Membership: m, Timeout: time.Second}
s.attach(context.Background(), l)
s.Attach(context.Background(), l)
if !s.SayFacts(context.Background(), Report{Firewall: "none"}) || !s.SayFacts(context.Background(), Report{}) {
t.Fatal("the facts were not taken")
@@ -85,7 +85,7 @@ func TestTheFactsEventCarriesTheNodeEnginesWriterEpochAndSequence(t *testing.T)
// A later start orders after this one with nothing kept on disk.
time.Sleep(2 * time.Millisecond)
later := &Session{Membership: m, Timeout: time.Second}
later.attach(context.Background(), l)
later.Attach(context.Background(), l)
later.SayFacts(context.Background(), Report{})
if last := l.said()[2]; last.Epoch <= got[1].Epoch {
t.Fatalf("a later start has epoch %d, not above %d", last.Epoch, got[1].Epoch)
@@ -100,8 +100,8 @@ func TestFactsWithNoLinkOpenAreNotSaid(t *testing.T) {
if s.SayFacts(context.Background(), Report{}) {
t.Fatal("facts were said with no link")
}
s.attach(context.Background(), l)
s.detach(l)
s.Attach(context.Background(), l)
s.Detach(l)
if s.Linked() || s.SayFacts(context.Background(), Report{}) || len(l.said()) != 0 {
t.Fatal("facts were said on a link that was let go")
}
@@ -113,7 +113,7 @@ func TestALinkOpeningSaysTheFacts(t *testing.T) {
l := newQuietLink()
opened := make(chan struct{}, 1)
s := &Session{Membership: m, Timeout: time.Second, OnLink: func(context.Context) { opened <- struct{}{} }}
s.attach(context.Background(), l)
s.Attach(context.Background(), l)
select {
case <-opened:
case <-time.After(2 * time.Second):