Merge main, and close what the confirmation review found (hq ADR 0272)
- Any failure of a request the link may have delivered now says the line may have run; "nothing ran" is said only of a request the link knows it never asked (no link open, nobody listening: link.ErrNotAsked). - A test holds that a connection saying nothing is answered within its deadline, set at accept, and gives its slot back. - The README says the socket refuses a process outside the machine's own namespaces, so flatpak, toolbox and distrobox shells are refused.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/cli"
|
||||
"github.com/novox/mesh-host/internal/link"
|
||||
)
|
||||
|
||||
// The controller answers a line as it answers every call (novox/hq ADR 0272, review): in an envelope, `result` or
|
||||
@@ -15,6 +16,9 @@ import (
|
||||
// followed by the same account on the same node until it ends. Its side holds the same field names (mesh-controller
|
||||
// internal/link, the mesh-cli field names' test).
|
||||
|
||||
// ErrNotAsked is the link's: a request it knows it never delivered.
|
||||
var ErrNotAsked = link.ErrNotAsked
|
||||
|
||||
// AskFunc asks the controller one request on this node's subject and returns its reply.
|
||||
type AskFunc func(ctx context.Context, body []byte) ([]byte, error)
|
||||
|
||||
@@ -40,11 +44,12 @@ func AskTheController(ctx context.Context, ask AskFunc, asked Asked) cli.Answer
|
||||
body, _ := json.Marshal(asked)
|
||||
reply, err := ask(ctx, body)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || ctx.Err() != nil {
|
||||
return cli.NotKnown("", "no answer from the controller in time")
|
||||
// **Only a request the link knows it never asked ran nothing**: no link open, nobody listening. Any other
|
||||
// failure may come after the request was delivered, and the line may have run (review of ADR 0272).
|
||||
if errors.Is(err, ErrNotAsked) {
|
||||
return cli.Refusal("the controller was not reached, so nothing ran: " + err.Error())
|
||||
}
|
||||
// Never delivered to a controller: no link, no subscriber, the bus refusing the publish.
|
||||
return cli.Refusal("the controller was not reached, so nothing ran: " + err.Error())
|
||||
return cli.NotKnown("", "no answer from the controller ("+err.Error()+")")
|
||||
}
|
||||
call := ""
|
||||
for {
|
||||
|
||||
@@ -83,16 +83,41 @@ func TestNoAnswerInTimeSaysTheLineMayHaveRun(t *testing.T) {
|
||||
// A request the bus never carried to a controller ran nothing, and says so.
|
||||
func TestARequestNobodyTookSaysNothingRan(t *testing.T) {
|
||||
c := &controllerSays{script: []func(Asked) ([]byte, error){func(Asked) ([]byte, error) {
|
||||
return nil, fmt.Errorf("nothing on this machine answers mesh.control.laptop.cli")
|
||||
return nil, fmt.Errorf("%w: nothing on this machine answers mesh.control.laptop.cli", ErrNotAsked)
|
||||
}}}
|
||||
a := AskTheController(context.Background(), c.ask, Asked{Line: []string{"status"}, Account: "op"})
|
||||
if !strings.Contains(a.Refused, "nothing ran") || a.Unknown != "" {
|
||||
t.Fatalf("a request nobody took reads %+v", a)
|
||||
}
|
||||
c = &controllerSays{script: []func(Asked) ([]byte, error){func(Asked) ([]byte, error) {
|
||||
return nil, errors.New("this machine has no link to the bus open now")
|
||||
return nil, fmt.Errorf("%w: this machine has no link to the bus open now", ErrNotAsked)
|
||||
}}}
|
||||
if a := AskTheController(context.Background(), c.ask, Asked{Line: []string{"status"}}); !strings.Contains(a.Refused, "nothing ran") {
|
||||
t.Fatalf("no link reads %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// **Any error once the request may have been delivered is not known, never "nothing ran"** (review of ADR 0272):
|
||||
// only a request the link says it never asked — no link open, nobody listening — ran nothing.
|
||||
func TestOnlyARequestNeverAskedSaysNothingRan(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
err error
|
||||
nothing bool
|
||||
}{
|
||||
{fmt.Errorf("%w: this machine has no link to the bus open now", ErrNotAsked), true},
|
||||
{fmt.Errorf("%w: nothing on this machine answers mesh.control.laptop.cli", ErrNotAsked), true},
|
||||
{errors.New("nats: connection closed"), false},
|
||||
{errors.New("mesh.control.laptop.cli: permissions violation"), false},
|
||||
{context.DeadlineExceeded, false},
|
||||
} {
|
||||
err := c.err
|
||||
ctl := &controllerSays{script: []func(Asked) ([]byte, error){func(Asked) ([]byte, error) { return nil, err }}}
|
||||
a := AskTheController(context.Background(), ctl.ask, Asked{Line: []string{"push"}, Account: "op"})
|
||||
if c.nothing && (!strings.Contains(a.Refused, "nothing ran") || a.Unknown != "") {
|
||||
t.Errorf("%v reads %+v; it was never asked", err, a)
|
||||
}
|
||||
if !c.nothing && (a.Refused != "" || !strings.Contains(a.Unknown, "may have run")) {
|
||||
t.Errorf("%v reads %+v; it may have been asked", err, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package meshcli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/cli"
|
||||
)
|
||||
|
||||
// **A connection that says nothing is bounded from the moment it is accepted** (review of ADR 0272): its deadline is
|
||||
// set before any lookup, so it is answered and closed within readWithin, holds no slot after, and asks nothing.
|
||||
func TestAConnectionThatSaysNothingIsAnsweredWithinItsDeadline(t *testing.T) {
|
||||
was := readWithin
|
||||
readWithin = 200 * time.Millisecond
|
||||
t.Cleanup(func() { readWithin = was })
|
||||
asked := false
|
||||
socket := serving(t, func(context.Context, Asked) cli.Answer {
|
||||
asked = true
|
||||
return cli.Answer{}
|
||||
})
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
a, err := cli.ReadAnswer(conn)
|
||||
if err != nil {
|
||||
t.Fatalf("a silent connection was not answered within its deadline: %v", err)
|
||||
}
|
||||
if asked || a.Refused == "" || !strings.Contains(a.Refused, "nothing was asked") {
|
||||
t.Fatalf("a silent connection was asked (%v) or answered %+v", asked, a)
|
||||
}
|
||||
// Its slot was given back: the account's next line is not busy.
|
||||
b, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
if err != nil || strings.Contains(b.Refused, "busy") || !asked {
|
||||
t.Fatalf("the next line after a silent one reads %+v (%v), asked %v", b, err, asked)
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ type Asked struct {
|
||||
type Forward func(ctx context.Context, asked Asked) cli.Answer
|
||||
|
||||
// readWithin bounds how long a connection may take to say its line: a program that connects and says nothing
|
||||
// holds nothing for long.
|
||||
const readWithin = 30 * time.Second
|
||||
// holds nothing for long. A variable so a test need not wait.
|
||||
var readWithin = 30 * time.Second
|
||||
|
||||
// atOnce bounds how many lines are asked at the same time, and perAccount how many of them one account's: a person
|
||||
// types one at a time, so more is a program on the machine. One over either bound is answered busy at once — the
|
||||
|
||||
Reference in New Issue
Block a user