Files
felhom-controller/controller/internal/agentapi/linetest.go
T
admin 046b808792
gates / gates (push) Successful in 1m22s
upwatch: hold a seeding torrent app for one line test, then put its limit back exactly (R-1250)
When the agent waits for quiet and qBittorrent or Transmission runs: record the limit first,
hold at 1 KiB/s, ask the agent for a test on request, put the old limit back (deferred; a
household change during the test stays). Start-up puts back a limit a killed controller left;
the 50 % rule skips while a hold record exists. Commit only.
2026-10-24 13:19:07 +02:00

70 lines
2.8 KiB
Go

package agentapi
import (
"context"
"encoding/json"
"fmt"
"time"
)
// LineTestState mirrors the agent's GET /host/line-test (agent ≥ 0.164.0; felhom.eu R-1247): the host's weekly upload
// line test — a 10-second iperf3 upload to ep0 inside the offsite tunnel, best of three runs, as ep0's server counted
// it. Present=false: never measured (no tunnel, not yet due, or disabled). An agent before the route answers 404 (a
// *StatusError); all three mean UNKNOWN, and the controller then sets no qBittorrent limit.
type LineTestState struct {
Present bool `json:"present"`
Mbps float64 `json:"mbps,omitempty"`
MeasuredAt string `json:"measured_at,omitempty"` // RFC3339 UTC
Method string `json:"method,omitempty"`
// WaitingForQuiet (agent ≥ 0.165.0, R-1250): a test is due and could start now, but the host's uplink has not been
// quiet for 10 minutes. With a seeding torrent app the controller then holds the app's limit and calls LineTestRun.
WaitingForQuiet bool `json:"waiting_for_quiet,omitempty"`
}
// LineTestRunResult mirrors POST /host/line-test/run. OK=false with Refused: nothing ran (not-due, outside-window,
// tries-used, no-tunnel, heavy-op, not-quiet, server-busy); OK=false without it: the runs failed.
type LineTestRunResult struct {
OK bool `json:"ok"`
Mbps float64 `json:"mbps,omitempty"`
RunsMbps []float64 `json:"runs_mbps,omitempty"`
Refused string `json:"refused,omitempty"`
Error string `json:"error,omitempty"`
}
// LineTestRunTimeout bounds the synchronous request: about 55 s on the agent (5 s settle, 10 s quiet check, three
// 10-second runs 5 s apart), each run itself bounded at 25 s.
const LineTestRunTimeout = 2 * time.Minute
// LineTestRun calls POST /host/line-test/run (R-1250): a test on request while the caller holds its torrent app's
// upload down. An agent before the route answers 404 (*StatusError).
func (c *Client) LineTestRun(ctx context.Context) (LineTestRunResult, error) {
var out LineTestRunResult
body, err := c.postSlow(ctx, "/host/line-test/run", struct{}{}, LineTestRunTimeout)
if err != nil {
return out, err
}
if err := json.Unmarshal(body, &out); err != nil {
return out, fmt.Errorf("agentapi: decode /host/line-test/run: %w", err)
}
return out, nil
}
// LineTest calls GET /host/line-test.
func (c *Client) LineTest(ctx context.Context) (LineTestState, error) {
var out LineTestState
body, err := c.get(ctx, "/host/line-test")
if err != nil {
return out, err
}
if err := json.Unmarshal(body, &out); err != nil {
return out, fmt.Errorf("agentapi: decode /host/line-test: %w", err)
}
return out, nil
}
// MeasuredAtTime parses MeasuredAt (zero time when absent or garbled).
func (s LineTestState) MeasuredAtTime() time.Time {
t, _ := time.Parse(time.RFC3339, s.MeasuredAt)
return t
}