A node's declaration consumer, which only the mesh can make
A host's account reaches no part of the JetStream API — correctly, because the controller is the only writer of consumer definitions — so the object a node reads its declarations through has to be waiting before the host binds to it, and nothing created one. Named after the node, because the node's own ack grant is `$JS.ACK.NODES.<node>.>` and a consumer named anything else is one the host cannot acknowledge a delivery from. No max-deliver, and a five-minute ack wait: a declaration is settled only after the node has applied it and reported, which is minutes on a machine pulling images, and the stream holds exactly one message per node — so there is nothing to dead-letter, only one message to redeliver for as long as that node is away. Asserted on start as well as created at enrolment, for the reason the streams are: a mesh raised from a restored backup has node records and no consumers, and a node whose consumer is missing hears nothing while everything else about it looks correct. The test sets the consumer against the grant the node actually gets, because each of the three ways of getting it wrong is silent: a wrong name cannot ack, a wrong filter reads another node's declarations, and a pull consumer is one a host has no authority to bind.
This commit is contained in:
@@ -146,6 +146,45 @@ func HolderConsumerFor(node, module string, seat DeclaredSeat) (Consumer, bool)
|
|||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeConsumer is the durable consumer a node reads its own declaration through.
|
||||||
|
//
|
||||||
|
// **Derived from a node existing, and created by the controller, because a host cannot create it.**
|
||||||
|
// A host's account may subscribe its own declaration subject and publish its own ack subject, and
|
||||||
|
// reaches no part of the JetStream API — which is correct (the controller is the only writer of
|
||||||
|
// consumer definitions, design 25 §3) and means the consumer must be waiting before the host binds
|
||||||
|
// to it. Named after the node, because the node's ack grant is `$JS.ACK.NODES.<node>.>` and a
|
||||||
|
// consumer named anything else is one the host cannot acknowledge a delivery from.
|
||||||
|
//
|
||||||
|
// **No max-deliver, and a long ack wait.** A declaration is settled only after the node has applied
|
||||||
|
// it and reported, which is minutes on a machine pulling images; and a declaration the mesh cannot
|
||||||
|
// get a node to accept is not one to dead-letter, because the stream keeps only the newest per node
|
||||||
|
// anyway — so there is exactly one message per node to redeliver, for as long as that node is away.
|
||||||
|
func NodeConsumer(node string) Consumer {
|
||||||
|
return Consumer{
|
||||||
|
Name: node,
|
||||||
|
Stream: "NODES",
|
||||||
|
Filters: []string{"mesh.node." + node + ".declare"},
|
||||||
|
Push: true,
|
||||||
|
AckWaitSeconds: 300,
|
||||||
|
Why: "how " + node + " hears what it should be; last-per-subject, so a node that was away " +
|
||||||
|
"gets exactly the current declaration and nothing older",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertNodeConsumers brings every known node's declaration consumer into being.
|
||||||
|
//
|
||||||
|
// Asserted on start as well as created at enrolment, for the reason the streams are: a mesh raised
|
||||||
|
// from a restored backup, or one whose bus was recreated, has node records and no consumers, and a
|
||||||
|
// node whose consumer is missing hears nothing while everything else about it looks correct.
|
||||||
|
func AssertNodeConsumers(e Ensurer, nodes []string) error {
|
||||||
|
for _, n := range nodes {
|
||||||
|
if err := e.EnsureConsumer(NodeConsumer(n)); err != nil {
|
||||||
|
return fmt.Errorf("asserting how %s hears its declaration: %w", n, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AllOverlaps reports subject filters claimed by more than one stream, across the mesh's own and
|
// AllOverlaps reports subject filters claimed by more than one stream, across the mesh's own and
|
||||||
// every derived one.
|
// every derived one.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -117,3 +117,39 @@ func TestASeatsStreamIsNamedAfterTheSeat(t *testing.T) {
|
|||||||
t.Fatalf("unexpected stream name %q", name)
|
t.Fatalf("unexpected stream name %q", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A node hears its declaration through a consumer only the controller can make.
|
||||||
|
//
|
||||||
|
// The three things that would each break it silently: a name other than the node's is one the host
|
||||||
|
// cannot acknowledge a delivery from, because its ack grant is derived from the node's name; a
|
||||||
|
// filter other than its own declaration subject is a node reading another's; and a pull consumer is
|
||||||
|
// one the host cannot bind a channel to without creating something, which it has no authority for.
|
||||||
|
func TestANodesDeclarationConsumerIsWhatItsOwnGrantAllows(t *testing.T) {
|
||||||
|
c := NodeConsumer("anchor")
|
||||||
|
if c.Name != "anchor" {
|
||||||
|
t.Fatalf("named %q, so the node cannot ack from it: its grant is $JS.ACK.NODES.anchor.>", c.Name)
|
||||||
|
}
|
||||||
|
if c.Stream != "NODES" {
|
||||||
|
t.Fatalf("on stream %q rather than the one declarations live in", c.Stream)
|
||||||
|
}
|
||||||
|
if len(c.Filters) != 1 || c.Filters[0] != "mesh.node.anchor.declare" {
|
||||||
|
t.Fatalf("filters %v, which is not this node's own declaration and nothing else", c.Filters)
|
||||||
|
}
|
||||||
|
if !c.Push {
|
||||||
|
t.Fatal("pulled, which a host cannot do: pulling needs the JetStream API and a host reaches none of it")
|
||||||
|
}
|
||||||
|
if c.MaxDeliver != 0 {
|
||||||
|
t.Fatalf("max-deliver %d: a declaration a node has not taken yet is not one to dead-letter, "+
|
||||||
|
"because the stream holds exactly one per node", c.MaxDeliver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// And the grant the node actually gets has to match, or none of the above matters.
|
||||||
|
perms, err := PermissionsFor(Principal{Kind: KindNode, Node: "anchor"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Without the ack grant every declaration a node receives is redelivered for ever; without the
|
||||||
|
// subscribe grant its consumer delivers to nobody.
|
||||||
|
has(t, perms.Publish, "$JS.ACK.NODES."+c.Name+".>")
|
||||||
|
has(t, perms.Subscribe, c.Filters[0])
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user