mesh-cli: follow a line to its answer, bound each account, refuse other namespaces (review of hq ADR 0272)
- A request that timed out was said as "the controller was not reached, nothing ran", though the line may have run. The engine now follows a line the controller answers as running, by its call, until it ends; when no answer comes in time it says the line may have run and names the call. "Nothing ran" is said only of a request no controller took. - One account could hold the socket: accepting waited for a free slot, and a connection could stall before its deadline was set. The deadline is set at accept, before any lookup; each user id has two lines at once and the machine eight, and one over is answered busy at once. - A container running with the operator's user id shares the number, not the machine: a peer whose mount or user namespace is not the engine's is refused, and nothing is asked. SDK pinned at the go/cli branch's newest commit until go/v0.1.9 is tagged.
This commit is contained in:
@@ -42,9 +42,9 @@ func serving(t *testing.T, forward Forward) string {
|
||||
// The account sent is the one the kernel gives for the connecting process, and the answer comes back as it was.
|
||||
func TestTheAccountAskingIsTheKernelsAndTheAnswerComesBack(t *testing.T) {
|
||||
var got Asked
|
||||
socket := serving(t, func(_ context.Context, asked Asked) (cli.Answer, error) {
|
||||
socket := serving(t, func(_ context.Context, asked Asked) cli.Answer {
|
||||
got = asked
|
||||
return cli.Answer{Stdout: []byte("done\n"), Exit: 0, Terminal: true, Why: "the terminal"}, nil
|
||||
return cli.Answer{Stdout: []byte("done\n"), Exit: 0, Terminal: true, Why: "the terminal"}
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"settings", "show", "zsh"})
|
||||
if err != nil {
|
||||
@@ -71,12 +71,12 @@ func TestTheAccountAskingIsTheKernelsAndTheAnswerComesBack(t *testing.T) {
|
||||
// Credentials the kernel did not give are no account: refused, and nothing is asked.
|
||||
func TestAConnectionWhoseAccountCannotBeReadIsNeverAskedAsAnyone(t *testing.T) {
|
||||
before := peerOf
|
||||
peerOf = func(*net.UnixConn) (uint32, error) { return 0, errors.New("no credentials") }
|
||||
peerOf = func(*net.UnixConn) (uint32, int32, error) { return 0, 0, errors.New("no credentials") }
|
||||
t.Cleanup(func() { peerOf = before })
|
||||
asked := false
|
||||
socket := serving(t, func(context.Context, Asked) (cli.Answer, error) {
|
||||
socket := serving(t, func(context.Context, Asked) cli.Answer {
|
||||
asked = true
|
||||
return cli.Answer{}, nil
|
||||
return cli.Answer{}
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
if err != nil {
|
||||
@@ -87,26 +87,12 @@ func TestAConnectionWhoseAccountCannotBeReadIsNeverAskedAsAnyone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A controller out of reach is said: nothing ran, and why.
|
||||
func TestAControllerOutOfReachIsSaidAsNothingRan(t *testing.T) {
|
||||
socket := serving(t, func(context.Context, Asked) (cli.Answer, error) {
|
||||
return cli.Answer{}, errors.New("this machine has no link to the bus open now")
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(a.Refused, "nothing ran") || !strings.Contains(a.Refused, "no link") || a.Exit != 1 {
|
||||
t.Fatalf("an unreached controller was not said: %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// A request over the bound is refused and never asked.
|
||||
func TestARequestOverTheBoundIsRefused(t *testing.T) {
|
||||
asked := false
|
||||
socket := serving(t, func(context.Context, Asked) (cli.Answer, error) {
|
||||
socket := serving(t, func(context.Context, Asked) cli.Answer {
|
||||
asked = true
|
||||
return cli.Answer{}, nil
|
||||
return cli.Answer{}
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{strings.Repeat("x", cli.MaxRequest)})
|
||||
if err != nil {
|
||||
@@ -143,6 +129,11 @@ func TestTheRequestToTheControllerKeepsItsFieldNames(t *testing.T) {
|
||||
if got := keysOf(t, body); got != "account line uid" {
|
||||
t.Fatalf("the request's fields are %q", got)
|
||||
}
|
||||
body, _ = json.Marshal(Asked{Follow: "call-1", Account: "a", UID: 1})
|
||||
if got := keysOf(t, body); got != "account follow uid" {
|
||||
t.Fatalf("a follow's fields are %q", got)
|
||||
}
|
||||
// What the controller answers as a line's result, and as a running line's: the same names its side holds.
|
||||
body, err = json.Marshal(cli.Answer{Stdout: []byte("o"), Stderr: []byte("e"), Exit: 1, Terminal: true, Why: "w",
|
||||
Refused: "r", Cut: true})
|
||||
if err != nil {
|
||||
@@ -151,6 +142,10 @@ func TestTheRequestToTheControllerKeepsItsFieldNames(t *testing.T) {
|
||||
if got := keysOf(t, body); got != "cut exit refused stderr stdout terminal why" {
|
||||
t.Fatalf("the answer's fields are %q", got)
|
||||
}
|
||||
body, _ = json.Marshal(runningResult{Running: true, Call: "call-1"})
|
||||
if got := keysOf(t, body); got != "call running" {
|
||||
t.Fatalf("a running answer's fields are %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func keysOf(t *testing.T, body []byte) string {
|
||||
@@ -166,3 +161,58 @@ func keysOf(t *testing.T, body []byte) string {
|
||||
sort.Strings(keys)
|
||||
return strings.Join(keys, " ")
|
||||
}
|
||||
|
||||
// **One account cannot hold the socket** (review of ADR 0272): its lines beyond its own bound are answered busy at
|
||||
// once, nothing is asked, and the engine goes on accepting.
|
||||
func TestOneAccountsLinesBeyondItsBoundAreAnsweredBusy(t *testing.T) {
|
||||
was := perAccount
|
||||
perAccount = 1
|
||||
t.Cleanup(func() { perAccount = was })
|
||||
release := make(chan struct{})
|
||||
started := make(chan struct{}, 2)
|
||||
socket := serving(t, func(context.Context, Asked) cli.Answer {
|
||||
started <- struct{}{}
|
||||
<-release
|
||||
return cli.Answer{}
|
||||
})
|
||||
// Released before the engine is stopped: cleanups run last first, and the engine waits for its lines.
|
||||
t.Cleanup(func() { close(release) })
|
||||
go func() { _, _ = cli.Ask(context.Background(), socket, []string{"push"}) }()
|
||||
<-started
|
||||
done := make(chan cli.Answer, 1)
|
||||
go func() {
|
||||
a, _ := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
done <- a
|
||||
}()
|
||||
select {
|
||||
case a := <-done:
|
||||
if !strings.Contains(a.Refused, "busy") {
|
||||
t.Fatalf("a second line from the same account reads %+v", a)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("a second line from the same account waited instead of being answered busy")
|
||||
}
|
||||
if len(started) != 0 {
|
||||
t.Fatal("the busy line was asked")
|
||||
}
|
||||
}
|
||||
|
||||
// **A process in another namespace is not this machine's account** (review of ADR 0272): a container running with
|
||||
// the operator's user id shares the id and not the machine; refused, and nothing is asked.
|
||||
func TestAPeerInAnotherNamespaceIsRefused(t *testing.T) {
|
||||
before := sameNamespaces
|
||||
sameNamespaces = func(pid int32) error { return errors.New("its mount namespace is not the engine's") }
|
||||
t.Cleanup(func() { sameNamespaces = before })
|
||||
asked := false
|
||||
socket := serving(t, func(context.Context, Asked) cli.Answer {
|
||||
asked = true
|
||||
return cli.Answer{}
|
||||
})
|
||||
a, err := cli.Ask(context.Background(), socket, []string{"status"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if asked || !strings.Contains(a.Refused, "namespace") {
|
||||
t.Fatalf("a peer in another namespace was asked (%v): %+v", asked, a)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user