ddc07609cb
P2R-13a. restic.Env gains LimitUploadKBps/LimitDownloadKBps which are
emitted as global --limit-upload/--limit-download flags before the
subcommand on every invocation. Agent dispatcher tracks host-wide
caps received via config.update; server pushes them on hello and
after PUT /api/hosts/{id}/bandwidth.
Also extends api.CommandRunPayload with optional per-job overrides
(BandwidthUpKBps/Down + PreHook/PostHook); the override consumers
land in T2/T6.
68 lines
2.2 KiB
Go
68 lines
2.2 KiB
Go
// host_bandwidth.go — REST API for /api/hosts/{id}/bandwidth.
|
|
//
|
|
// Host-wide upload/download caps (KB/s). Applied to every restic
|
|
// invocation as --limit-upload / --limit-download. Pass null /
|
|
// omit a field to clear that cap.
|
|
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
stdhttp "net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"gitea.dcglab.co.uk/steve/restic-manager/internal/store"
|
|
)
|
|
|
|
type hostBandwidthRequest struct {
|
|
BandwidthUpKBps *int `json:"bandwidth_up_kbps"`
|
|
BandwidthDownKBps *int `json:"bandwidth_down_kbps"`
|
|
}
|
|
|
|
type hostBandwidthView struct {
|
|
BandwidthUpKBps *int `json:"bandwidth_up_kbps"`
|
|
BandwidthDownKBps *int `json:"bandwidth_down_kbps"`
|
|
}
|
|
|
|
func (s *Server) handleUpdateHostBandwidth(w stdhttp.ResponseWriter, r *stdhttp.Request) {
|
|
if !s.authedUser(r) {
|
|
writeJSONError(w, stdhttp.StatusUnauthorized, "unauthorized", "")
|
|
return
|
|
}
|
|
hostID := chi.URLParam(r, "id")
|
|
if _, err := s.deps.Store.GetHost(r.Context(), hostID); err != nil {
|
|
if errors.Is(err, store.ErrNotFound) {
|
|
writeJSONError(w, stdhttp.StatusNotFound, "host_not_found", "")
|
|
return
|
|
}
|
|
writeJSONError(w, stdhttp.StatusInternalServerError, "internal", "")
|
|
return
|
|
}
|
|
var req hostBandwidthRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSONError(w, stdhttp.StatusBadRequest, "invalid_json", err.Error())
|
|
return
|
|
}
|
|
if req.BandwidthUpKBps != nil && *req.BandwidthUpKBps < 0 {
|
|
writeJSONError(w, stdhttp.StatusBadRequest, "invalid_value",
|
|
"bandwidth_up_kbps must be non-negative")
|
|
return
|
|
}
|
|
if req.BandwidthDownKBps != nil && *req.BandwidthDownKBps < 0 {
|
|
writeJSONError(w, stdhttp.StatusBadRequest, "invalid_value",
|
|
"bandwidth_down_kbps must be non-negative")
|
|
return
|
|
}
|
|
if err := s.deps.Store.SetHostBandwidth(r.Context(), hostID, req.BandwidthUpKBps, req.BandwidthDownKBps); err != nil {
|
|
writeJSONError(w, stdhttp.StatusInternalServerError, "internal", err.Error())
|
|
return
|
|
}
|
|
// Fan out to the agent if connected. Errors are non-fatal — the
|
|
// next reconnect's onAgentHello will resync.
|
|
if s.deps.Hub != nil && s.deps.Hub.Connected(hostID) {
|
|
_ = s.pushBandwidthToAgent(r.Context(), hostID, req.BandwidthUpKBps, req.BandwidthDownKBps)
|
|
}
|
|
writeJSON(w, stdhttp.StatusOK, hostBandwidthView(req))
|
|
}
|