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:
@@ -0,0 +1,87 @@
|
||||
package meshcli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/cli"
|
||||
)
|
||||
|
||||
// The controller answers a line as it answers every call (novox/hq ADR 0272, review): in an envelope, `result` or
|
||||
// `error`, and within its window for an answer — so a line still running is answered `running` with its call, and
|
||||
// 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).
|
||||
|
||||
// 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)
|
||||
|
||||
// followEvery is how often a line still running is asked about again.
|
||||
var followEvery = 2 * time.Second
|
||||
|
||||
// envelope is a call's answer: its result, or its error.
|
||||
type envelope struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// runningResult is what a call still running answers in its result.
|
||||
type runningResult struct {
|
||||
Running bool `json:"running"`
|
||||
Call string `json:"call"`
|
||||
}
|
||||
|
||||
// AskTheController asks the line and follows it to its answer, within ctx. What it says is one of three things,
|
||||
// never confused: what the line printed; that nothing ran, and why (the request was never taken); or that what came
|
||||
// of it is not known — it may have run — naming the call to look at when one is known.
|
||||
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")
|
||||
}
|
||||
// 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())
|
||||
}
|
||||
call := ""
|
||||
for {
|
||||
var env envelope
|
||||
if err := json.Unmarshal(reply, &env); err != nil {
|
||||
return cli.NotKnown(call, "the controller's answer could not be read")
|
||||
}
|
||||
if env.Error != "" {
|
||||
return cli.NotKnown(call, "the controller answered "+env.Error)
|
||||
}
|
||||
var r runningResult
|
||||
_ = json.Unmarshal(env.Result, &r)
|
||||
if !r.Running {
|
||||
var a cli.Answer
|
||||
if err := json.Unmarshal(env.Result, &a); err != nil {
|
||||
return cli.NotKnown(call, "the controller's answer could not be read")
|
||||
}
|
||||
return a
|
||||
}
|
||||
if r.Call != "" {
|
||||
call = r.Call
|
||||
}
|
||||
if call == "" {
|
||||
return cli.NotKnown("", "the controller says the line is running and names no call to follow")
|
||||
}
|
||||
// Asked again until it ends, through a link that may drop and come back: a failed follow is tried
|
||||
// again, and only ctx ending stops it.
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return cli.NotKnown(call, fmt.Sprintf("no answer within the time mesh-cli waits (%s)", cli.AskWithin))
|
||||
case <-time.After(followEvery):
|
||||
}
|
||||
body, _ := json.Marshal(Asked{Follow: call, Account: asked.Account, UID: asked.UID})
|
||||
if reply, err = ask(ctx, body); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user