Manage external site-prober actions va API and Dashboard
This commit is contained in:
1 parent
f22ad569b6
commit
42f584dd3c
28 files changed
+717
-32
No files matched your search
@@ -80,3 +80,11 @@ type putCheckTypeRequest struct {
|
||||
type orchestratorSettingsDTO struct {
|
||||
FIPSettleSeconds int `json:"fip_settle_seconds"`
|
||||
}
|
||||
|
||||
// inboundChecksDTO doubles as both the GET response and the PUT request
|
||||
// body for /api/v1/admin/config/inbound-checks. Same field shape as
|
||||
// proberAssignment.Ports/ICMP (dto.go).
|
||||
type inboundChecksDTO struct {
|
||||
Ports []int `json:"ports"`
|
||||
ICMP bool `json:"icmp"`
|
||||
}
|
||||
@@ -209,3 +209,30 @@ func (s *Server) handleConfigPutOrchestratorSettings(w http.ResponseWriter, r *h
|
||||
}
|
||||
writeJSON(w, http.StatusOK, orchestratorSettingsDTO{FIPSettleSeconds: req.FIPSettleSeconds})
|
||||
}
|
||||
|
||||
// --- prober inbound checks ---
|
||||
|
||||
func (s *Server) handleConfigGetInboundChecks(w http.ResponseWriter, r *http.Request) {
|
||||
settings, err := s.DB.GetInboundChecks(r.Context())
|
||||
if err != nil {
|
||||
writeDBError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, inboundChecksDTO{Ports: settings.Ports, ICMP: settings.ICMP})
|
||||
}
|
||||
|
||||
// handleConfigPutInboundChecks writes directly to the DB (unlike the
|
||||
// orchestrator-settings PUT above) because port-range/duplicate validation
|
||||
// is purely local — no cross-field dependency on other orchestrator config.
|
||||
func (s *Server) handleConfigPutInboundChecks(w http.ResponseWriter, r *http.Request) {
|
||||
var req inboundChecksDTO
|
||||
if err := readJSON(r, &req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid body: "+err.Error())
|
||||
return
|
||||
}
|
||||
if err := s.DB.SetInboundChecks(r.Context(), req.Ports, req.ICMP); err != nil {
|
||||
writeDBError(w, err)
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, inboundChecksDTO{Ports: req.Ports, ICMP: req.ICMP})
|
||||
}
|
||||
@@ -500,3 +500,126 @@ func TestFIPSettleDelayGatesAssignmentEndpoint(t *testing.T) {
|
||||
t.Fatalf("expected 200 after settle window elapsed, status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInboundChecksGetPut proves the prober check config round-trips
|
||||
// through GET/PUT /api/v1/admin/config/inbound-checks.
|
||||
func TestInboundChecksGetPut(t *testing.T) {
|
||||
fc, _, _, _ := newConfigTestHarness(t)
|
||||
|
||||
resp, body := fc.do(http.MethodGet, "/api/v1/admin/config/inbound-checks", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("get inbound checks: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
var got inboundChecksDTO
|
||||
if err := json.Unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("unmarshal get response: %v", err)
|
||||
}
|
||||
// newConfigTestHarness seeds Ports:[22,80], ICMP:true.
|
||||
if len(got.Ports) != 2 || !got.ICMP {
|
||||
t.Fatalf("expected seeded {[22 80] true}, got %+v", got)
|
||||
}
|
||||
|
||||
resp, body = fc.do(http.MethodPut, "/api/v1/admin/config/inbound-checks", inboundChecksDTO{Ports: []int{443, 8080}, ICMP: false})
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("put inbound checks: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
if err := json.Unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("unmarshal put response: %v", err)
|
||||
}
|
||||
if len(got.Ports) != 2 || got.ICMP {
|
||||
t.Fatalf("expected {[443 8080] false}, got %+v", got)
|
||||
}
|
||||
|
||||
resp, body = fc.do(http.MethodGet, "/api/v1/admin/config/inbound-checks", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("get inbound checks after put: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
if err := json.Unmarshal(body, &got); err != nil {
|
||||
t.Fatalf("unmarshal get-after-put response: %v", err)
|
||||
}
|
||||
if got.Ports[0] != 443 || got.Ports[1] != 8080 || got.ICMP {
|
||||
t.Fatalf("expected {[443 8080] false} to persist, got %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInboundChecksPutValidation proves out-of-range and duplicate ports
|
||||
// are rejected with 400.
|
||||
func TestInboundChecksPutValidation(t *testing.T) {
|
||||
fc, _, _, _ := newConfigTestHarness(t)
|
||||
|
||||
resp, body := fc.do(http.MethodPut, "/api/v1/admin/config/inbound-checks", inboundChecksDTO{Ports: []int{0}, ICMP: false})
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for port 0, status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
resp, body = fc.do(http.MethodPut, "/api/v1/admin/config/inbound-checks", inboundChecksDTO{Ports: []int{70000}, ICMP: false})
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for port 70000, status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
resp, body = fc.do(http.MethodPut, "/api/v1/admin/config/inbound-checks", inboundChecksDTO{Ports: []int{22, 22}, ICMP: false})
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for duplicate port, status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInboundChecksReflectedInProberAssignmentsWithoutRestart proves the
|
||||
// fix to the formerly-static handlers_prober.go read: a PUT to
|
||||
// /api/v1/admin/config/inbound-checks changes what GET
|
||||
// /api/v1/probers/{site_id}/assignments hands back to an already-registered
|
||||
// prober, for an IP already in `checking`, with no control-api restart.
|
||||
func TestInboundChecksReflectedInProberAssignmentsWithoutRestart(t *testing.T) {
|
||||
fc, _, orch, mock := newConfigTestHarness(t)
|
||||
ctx := context.Background()
|
||||
mock.Seed("fip-1", "9.9.9.9", "svc-project")
|
||||
|
||||
fc.do(http.MethodPut, "/api/v1/admin/config/sites/1", putSiteRequest{SiteID: "site-1"})
|
||||
fc.do(http.MethodPost, "/api/v1/admin/config/validators", createValidatorRequest{ValidatorID: "validator-1", OSPortID: "port-1"})
|
||||
fc.do(http.MethodPost, "/api/v1/agents/register", registerAgentRequest{ValidatorID: "validator-1"})
|
||||
fc.do(http.MethodPost, "/api/v1/admin/ips", submitIPsRequest{Addresses: []string{"9.9.9.9"}})
|
||||
|
||||
orch.Tick(ctx) // claim + associate -> awaiting_self_check
|
||||
|
||||
resp, body := fc.do(http.MethodGet, "/api/v1/agents/validator-1/assignment", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("assignment: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
var assignment assignmentResponse
|
||||
if err := json.Unmarshal(body, &assignment); err != nil {
|
||||
t.Fatalf("unmarshal assignment: %v", err)
|
||||
}
|
||||
fc.do(http.MethodPost, "/api/v1/agents/validator-1/self-check", selfCheckRequest{
|
||||
IPID: assignment.IPID, DetectedEgress: "9.9.9.9", Success: true, Detail: "matched",
|
||||
})
|
||||
// Now item.State == "checking" — a prober assignment target.
|
||||
|
||||
fc.do(http.MethodPost, "/api/v1/probers/register", registerProberRequest{SiteID: "site-1"})
|
||||
|
||||
resp, body = fc.do(http.MethodGet, "/api/v1/probers/site-1/assignments", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("prober assignments: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
var assignments []proberAssignment
|
||||
if err := json.Unmarshal(body, &assignments); err != nil {
|
||||
t.Fatalf("unmarshal assignments: %v", err)
|
||||
}
|
||||
if len(assignments) != 1 || len(assignments[0].Ports) != 2 || !assignments[0].ICMP {
|
||||
t.Fatalf("expected seeded {[22 80] true} before PUT, got %+v", assignments)
|
||||
}
|
||||
|
||||
resp, body = fc.do(http.MethodPut, "/api/v1/admin/config/inbound-checks", inboundChecksDTO{Ports: []int{8080}, ICMP: false})
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("put inbound checks: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
resp, body = fc.do(http.MethodGet, "/api/v1/probers/site-1/assignments", nil)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("prober assignments after put: status=%d body=%s", resp.StatusCode, body)
|
||||
}
|
||||
if err := json.Unmarshal(body, &assignments); err != nil {
|
||||
t.Fatalf("unmarshal assignments after put: %v", err)
|
||||
}
|
||||
if len(assignments) != 1 || len(assignments[0].Ports) != 1 || assignments[0].Ports[0] != 8080 || assignments[0].ICMP {
|
||||
t.Fatalf("expected updated {[8080] false} without restart, got %+v", assignments)
|
||||
}
|
||||
}
|
||||
@@ -45,11 +45,16 @@ func (s *Server) handleProberAssignments(w http.ResponseWriter, r *http.Request)
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
inbound, err := s.DB.GetInboundChecks(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
out := make([]proberAssignment, 0, len(items))
|
||||
for _, item := range items {
|
||||
out = append(out, proberAssignment{
|
||||
IPID: item.ID, IPAddress: item.IPAddress,
|
||||
Ports: s.Orch.Inbound.Ports, ICMP: s.Orch.Inbound.ICMP,
|
||||
Ports: inbound.Ports, ICMP: inbound.ICMP,
|
||||
})
|
||||
}
|
||||
writeJSON(w, http.StatusOK, out)
|
||||
|
||||
@@ -46,4 +46,7 @@ func (s *Server) routes(mux *http.ServeMux) {
|
||||
|
||||
mux.HandleFunc("GET /api/v1/admin/config/orchestrator", s.handleConfigGetOrchestratorSettings)
|
||||
mux.HandleFunc("PUT /api/v1/admin/config/orchestrator", s.handleConfigPutOrchestratorSettings)
|
||||
|
||||
mux.HandleFunc("GET /api/v1/admin/config/inbound-checks", s.handleConfigGetInboundChecks)
|
||||
mux.HandleFunc("PUT /api/v1/admin/config/inbound-checks", s.handleConfigPutInboundChecks)
|
||||
}
|
||||
Reference in new issue
Block a user