Read verbs now run in the serving process, where a panic would end every call; refused logins are nobody's reconnect loop (review of hq issue 327).
190 lines
6.3 KiB
Go
190 lines
6.3 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/nats-io/nats.go"
|
||
|
||
"github.com/novox/mesh-controller/internal/broker"
|
||
"github.com/novox/mesh-controller/internal/conditions"
|
||
"github.com/novox/mesh-controller/internal/link"
|
||
"github.com/novox/mesh-controller/internal/testbus"
|
||
)
|
||
|
||
// The serving controller's own connection serves what it does for a moment: no connection is opened,
|
||
// and closing what it was lent leaves the serving one open (novox/hq issue 327).
|
||
func TestTheServingControllerLendsItsOwnConnection(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
serving, err := broker.Dial(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer serving.Close()
|
||
if err := serving.EnsureControllerBuckets(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
was := servingBus.Load()
|
||
servingBus.Store(serving)
|
||
defer servingBus.Store(was)
|
||
t.Setenv(broker.NATSVar, bus.ClientURL())
|
||
|
||
before, _ := bus.Varz(nil)
|
||
lent, err := aBus()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
lent.Close()
|
||
if !serving.Conn().IsConnected() {
|
||
t.Fatal("closing a lent connection closed the serving controller's")
|
||
}
|
||
if err := onTheBus(func(*nats.Conn) error { return nil }); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
handlers, _, err := seatToolHandlers()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
answer, err := handlers["hand-acts"](context.Background(), json.RawMessage(`{}`))
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if a := answer.(verbAnswer); !a.OK || a.Answer == nil {
|
||
t.Fatalf("hand-acts answered %+v", a)
|
||
}
|
||
_, _ = handlers["queue"](context.Background(), json.RawMessage(`{}`))
|
||
after, _ := bus.Varz(nil)
|
||
if opened := after.TotalConnections - before.TotalConnections; opened != 0 {
|
||
t.Fatalf("the serving controller opened %d connection(s) of its own", opened)
|
||
}
|
||
}
|
||
|
||
// A verb that still runs as a process of its own says which in the bus's list of connections.
|
||
func TestAVerbsOwnProcessNamesItsConnection(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
setup, err := broker.Dial(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := setup.EnsureControllerBuckets(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
setup.Close()
|
||
asAProcess(t, bus.ClientURL())
|
||
handlers, _, err := seatToolHandlers()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// A silence acts, so it is still its own process; it dials, and finds no such condition.
|
||
_, _ = handlers["conditions"](context.Background(),
|
||
json.RawMessage(`{"silence":"bus.nothing.here","for":"1h","why":"a test"}`))
|
||
names := closedNames(t, bus)
|
||
found := false
|
||
for _, n := range names {
|
||
found = found || n == "mesh-controller verb conditions"
|
||
}
|
||
if !found {
|
||
t.Fatalf("the verb's connection was named %q", names)
|
||
}
|
||
}
|
||
|
||
func TestEachProcessNamesItsConnectionsForWhatItIs(t *testing.T) {
|
||
for _, c := range []struct{ command, verb, want string }{
|
||
{"serve", "", "mesh-controller serving"},
|
||
{"conditions", "conditions", "mesh-controller verb conditions"},
|
||
{"delivery", "delivery-check", "mesh-controller verb delivery-check"},
|
||
{"push", "", "mesh-controller command push"},
|
||
} {
|
||
if got := connectionName(c.command, c.verb); got != c.want {
|
||
t.Errorf("%s/%s: %q", c.command, c.verb, got)
|
||
}
|
||
}
|
||
}
|
||
|
||
// D15: a user whose connections are dropped more than twelve times an hour is said, in plain words; one
|
||
// whose client closes its own short connections is not.
|
||
func TestAClientReconnectingInALoopIsSaid(t *testing.T) {
|
||
var read closedConnections
|
||
if err := json.Unmarshal([]byte(`{"hours":1,"reaches":true,"users":[
|
||
{"user":"ace.node-tools","closed":40,"dropped":40,"dropped_per_hour":40,"names":[
|
||
{"name":"ace.node-tools","closed":40,"dropped":40,"reasons":{"Stale Connection":30,"Read Error":10}}]},
|
||
{"user":"controller","closed":300,"dropped":0,"names":[
|
||
{"name":"mesh-controller verb delivery-check","closed":300,"dropped":0,"reasons":{"Client Closed":300}}]},
|
||
{"user":"(no user: refused before it logged in)","closed":90,"dropped":90,"names":[{"name":"","closed":90,
|
||
"dropped":90,"reasons":{"Authentication Failure":90}}]},
|
||
{"user":"node.anchor","closed":5,"dropped":5,"names":[{"name":"mesh-host/anchor","closed":5,"dropped":5,
|
||
"reasons":{"Read Error":5}}]}]}`), &read); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
found := reconnecting(read)
|
||
if len(found) != 1 {
|
||
t.Fatalf("%+v", found)
|
||
}
|
||
o := found[0]
|
||
if o.ID != "ace.node-tools" || o.Machine != "ace" || o.Kind != kindBusReconnects ||
|
||
o.Headline != "The tool runner on ace keeps losing the bus" || !strings.Contains(o.Said, "Stale Connection ×30") {
|
||
t.Fatalf("%+v", o)
|
||
}
|
||
if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation,
|
||
Resolved: o.Resolved}, "ace"); !ok {
|
||
t.Fatalf("not plain: %s", why)
|
||
}
|
||
}
|
||
|
||
// The bus's module is asked on its machine, over the bus.
|
||
func TestTheBusModuleIsAskedWhoClosedConnections(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
conn, err := nats.Connect(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer conn.Close()
|
||
sub, err := conn.Subscribe(link.ModuleToolOn("nats", "nats_closed_connections", "anchor"), func(m *nats.Msg) {
|
||
_ = m.Respond([]byte(`{"result":{"hours":1,"reaches":true,"users":[{"user":"controller","closed":2,"dropped":0}]}}`))
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer func() { _ = sub.Unsubscribe() }()
|
||
read, err := askClosed(context.Background(), conn, "anchor")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(read.Users) != 1 || read.Users[0].Closed != 2 {
|
||
t.Fatalf("%+v", read)
|
||
}
|
||
}
|
||
|
||
// A bus module older than the tool answers nothing to the question: the probe passes over it quietly.
|
||
func TestAnOlderBusModuleIsPassedOverQuietly(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
conn, err := nats.Connect(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer conn.Close()
|
||
found, err := reconnectsOn(context.Background(), conn, "anchor")
|
||
if err != nil || len(found) != 0 {
|
||
t.Fatalf("a bus module without the tool: %v %v", found, err)
|
||
}
|
||
}
|
||
|
||
// A verb answered in the serving controller says its flag errors in its answer, not in the controller's log.
|
||
func TestAFlagErrorIsSaidInTheAnswer(t *testing.T) {
|
||
bus := testbus.Start(t)
|
||
serving, err := broker.Dial(bus.ClientURL())
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
defer serving.Close()
|
||
was := servingBus.Load()
|
||
servingBus.Store(serving)
|
||
defer servingBus.Store(was)
|
||
answer, read := readHere(context.Background(), []string{"hand-acts", "--bogus"})
|
||
if !read || answer.OK || !strings.Contains(answer.Output, "flag provided but not defined") {
|
||
t.Fatalf("answered %+v", answer)
|
||
}
|
||
}
|