A tie went to whichever tool discovery listed first, so docker logs found docker_secrets_in_logs before docker_logs on some runs. A name the query says whole wins, then the shorter name.
152 lines
7.3 KiB
Go
152 lines
7.3 KiB
Go
package console
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-tools/node-tools/internal/announce"
|
|
)
|
|
|
|
// meshToSearch is a mesh as discovery and the controller's records describe it: the service manager's
|
|
// verbs on two machines, the controller's own, a docker module with its tools, and what each replaces
|
|
// as the records say it — the seats' from `tools`, the module's from `modules`.
|
|
func meshToSearch(t *testing.T) *index {
|
|
t.Helper()
|
|
var endpoints []announce.Endpoint
|
|
verbs := map[string]string{
|
|
"units": "The units the service manager knows in a scope, each with its load, active and sub state.",
|
|
"status": "One unit as the service manager sees it now: its states, whether it starts at boot.",
|
|
"restart": "Restart one unit.",
|
|
"journal": "The last lines of one unit's journal (at most 2000).",
|
|
}
|
|
for _, node := range []string{"anchor", "laptop"} {
|
|
for verb, does := range verbs {
|
|
endpoints = append(endpoints, announce.Endpoint{Kind: announce.KindSeat, Module: "systemd", Seat: "node-service-manager",
|
|
Tool: verb, Scope: "node", Node: node, Description: does,
|
|
Subject: "mesh.seat.node-service-manager.tool." + verb + "." + node})
|
|
}
|
|
}
|
|
for verb, does := range map[string]string{
|
|
"node": "What one machine reported it can do, what it is assigned, and why.",
|
|
"nodes": "Every machine the mesh knows, with whether it is converged or adopted.",
|
|
} {
|
|
endpoints = append(endpoints, announce.Endpoint{Kind: announce.KindSeat, Module: "mesh-controller", Seat: "mesh-controller",
|
|
Tool: verb, Scope: "mesh", Description: does, Subject: "mesh.seat.mesh-controller.tool." + verb})
|
|
}
|
|
for tool, does := range map[string]string{
|
|
"docker_list": "Every container on this machine: name, image, state, ports.",
|
|
"docker_logs": "The last lines a container wrote.",
|
|
"docker_secrets_in_logs": "Whether a container's logs hold a secret.",
|
|
"docker_restart": "Restart one container.",
|
|
} {
|
|
endpoints = append(endpoints, announce.Endpoint{Kind: announce.KindTool, Module: "docker", Tool: tool, Node: "anchor",
|
|
Description: does, Subject: "mesh.mod.docker.tool." + tool + ".anchor"})
|
|
}
|
|
x, _, _ := indexOfEndpoints(endpoints)
|
|
tools := json.RawMessage(`{"seats":[{"seat":"node-service-manager","scope":"node","tools":[
|
|
{"name":"units","replaces":["systemctl list-units","systemctl --failed"]},
|
|
{"name":"status","replaces":["systemctl status","systemctl is-active"]},
|
|
{"name":"restart","replaces":["systemctl restart"]},
|
|
{"name":"journal","replaces":["journalctl"]}]},
|
|
{"seat":"mesh-controller","scope":"mesh","tools":[{"name":"node","replaces":["hostnamectl","uptime"]},{"name":"nodes"}]}]}`)
|
|
x.Replaces = replacesOf(tools, []recordedModule{{Module: "docker", On: []string{"anchor"}, Tools: true,
|
|
Replaces: map[string][]string{"docker_list": {"docker ps"}, "docker_logs": {"docker logs"}, "docker_restart": {"docker restart"}}}})
|
|
return x
|
|
}
|
|
|
|
// **The words an agent has find the verb first** (novox/hq ADR 0245): the command it would have run, or
|
|
// the word it would have used, puts the mesh's tool for it at the top.
|
|
func TestSearchFindsTheToolThatReplacesACommandFirst(t *testing.T) {
|
|
x := meshToSearch(t)
|
|
for query, want := range map[string]string{
|
|
"journalctl": "anchor/node-service-manager.journal",
|
|
"journalctl -u mesh-controller -n 50": "anchor/node-service-manager.journal",
|
|
"sudo journalctl -fu sshd": "anchor/node-service-manager.journal",
|
|
"/usr/bin/journalctl": "anchor/node-service-manager.journal",
|
|
"logs": "anchor/node-service-manager.journal",
|
|
"systemctl status": "anchor/node-service-manager.status",
|
|
"systemctl status nats.service": "anchor/node-service-manager.status",
|
|
"systemctl restart mesh-controller": "anchor/node-service-manager.restart",
|
|
"systemctl --failed": "anchor/node-service-manager.units",
|
|
"docker ps": "anchor/docker.docker_list",
|
|
"docker ps -a": "anchor/docker.docker_list",
|
|
"docker logs": "anchor/docker.docker_logs",
|
|
"docker logs --tail 100 nats": "anchor/docker.docker_logs",
|
|
"docker restart nats": "anchor/docker.docker_restart",
|
|
"container logs": "anchor/docker.docker_logs",
|
|
"uptime": "mesh-controller.node",
|
|
} {
|
|
hits := x.search(query)
|
|
if len(hits) == 0 {
|
|
t.Errorf("%q found nothing; want %s first", query, want)
|
|
continue
|
|
}
|
|
if hits[0].Address != want {
|
|
var got []string
|
|
for _, h := range hits {
|
|
got = append(got, h.Address)
|
|
}
|
|
t.Errorf("%q found %s first; want %s (all: %s)", query, hits[0].Address, want, strings.Join(got, ", "))
|
|
}
|
|
}
|
|
}
|
|
|
|
// A match on a replaced command says which command it replaced, and the most specific one wins.
|
|
func TestASearchByCommandSaysWhatItReplaces(t *testing.T) {
|
|
x := meshToSearch(t)
|
|
hits := x.search("systemctl status sshd")
|
|
if hits[0].InsteadOf != "systemctl status" {
|
|
t.Errorf("the match does not say what it replaces: %+v", hits[0])
|
|
}
|
|
if hits[0].Also != "also on laptop" {
|
|
t.Errorf("a node seat's verb does not say its other machines: %+v", hits[0])
|
|
}
|
|
// `logs` also finds the container's logs, after the journal — both are answers.
|
|
var docker bool
|
|
for _, h := range x.search("logs")[:3] {
|
|
docker = docker || h.Address == "anchor/docker.docker_logs"
|
|
}
|
|
if !docker {
|
|
t.Errorf("`logs` does not find docker_logs among the first: %+v", x.search("logs"))
|
|
}
|
|
}
|
|
|
|
// What search did before stays: every word in a name or description, and nothing for a word nothing has.
|
|
// A synonym is a word, never part of one: `logs` matches through `log`, and finds no `login`.
|
|
func TestSearchStillMatchesEveryWord(t *testing.T) {
|
|
x := meshToSearch(t)
|
|
if hits := x.search("machine converged"); len(hits) != 1 || hits[0].Address != "mesh-controller.nodes" {
|
|
t.Errorf("words in a description: %+v", hits)
|
|
}
|
|
if hits := x.search("journalctl kubernetes"); len(hits) != 1 || hits[0].Address != "anchor/node-service-manager.journal" {
|
|
t.Errorf("a command with arguments nothing has is still that command: %+v", hits)
|
|
}
|
|
if hits := x.search("kubernetes"); len(hits) != 0 {
|
|
t.Errorf("a word nothing has matched: %+v", hits)
|
|
}
|
|
x.Modules["shell"] = &moduleInfo{Module: "shell", On: []string{"anchor"}, Tools: []Tool{{Module: "shell", Name: "shell_login",
|
|
Description: "The account's login shell."}}}
|
|
for _, h := range x.search("logs") {
|
|
if h.Address == "anchor/shell.shell_login" {
|
|
t.Errorf("`logs` found `login` through its synonym `log`: %+v", h)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A controller older than `replaces` answers without it: search matches names and descriptions as before.
|
|
func TestSearchWithoutReplacesMatchesAsBefore(t *testing.T) {
|
|
x := meshToSearch(t)
|
|
x.Replaces = replacesOf(json.RawMessage(`{"seats":[{"seat":"node-service-manager","tools":[{"name":"journal"}]}]}`),
|
|
[]recordedModule{{Module: "docker"}})
|
|
if len(x.Replaces) != 0 {
|
|
t.Fatalf("replaces from records that have none: %v", x.Replaces)
|
|
}
|
|
if hits := x.search("journal"); len(hits) == 0 || hits[0].Address != "anchor/node-service-manager.journal" {
|
|
t.Errorf("a verb's own name: %+v", hits)
|
|
}
|
|
if hits := x.search("docker logs"); len(hits) == 0 || hits[0].Address != "anchor/docker.docker_logs" {
|
|
t.Errorf("a tool's own name: %+v", hits)
|
|
}
|
|
}
|