Manage external site-prober actions va API and Dashboard

This commit is contained in:
ayurishchev committed 2026-08-26 19:45:48 +03:00
1 parent f22ad569b6
commit 42f584dd3c
28 files changed
+717 -32

No files matched your search

+28 -24
View File
@@ -31,28 +31,28 @@ type CheckConfig struct {
}
type Orchestrator struct {
DB *db.DB
OS openstack.FloatingIPClient
Cfg config.OrchestratorConfig
Agg config.AggregationConfig
Inbound config.InboundConfig
Log *slog.Logger
DB *db.DB
OS openstack.FloatingIPClient
Cfg config.OrchestratorConfig
Agg config.AggregationConfig
Log *slog.Logger
}
// New constructs an Orchestrator. Egress check types/targets and prober
// sites are no longer taken from cfg — they're read from the database on
// every use (see AssignmentForValidator, expectedCheckCount,
// isReadyToAggregate) so admin API changes to them take effect without a
// restart. cfg.Validators/.Sites/.CheckTypes/.Targets/.IPAddresses are only
// consulted once, at process startup, by db.BootstrapFromConfig.
// New constructs an Orchestrator. Egress check types/targets, prober sites,
// and prober inbound check config (ports/icmp) are no longer taken from
// cfg — they're read from the database on every use (see
// AssignmentForValidator, expectedCheckCount, isReadyToAggregate,
// httpapi.handleProberAssignments) so admin API changes to them take effect
// without a restart. cfg.Validators/.Sites/.CheckTypes/.Targets/.Inbound/
// .IPAddresses are only consulted once, at process startup, by
// db.BootstrapFromConfig.
func New(d *db.DB, osClient openstack.FloatingIPClient, cfg *config.ControlAPI, log *slog.Logger) *Orchestrator {
return &Orchestrator{
DB: d,
OS: osClient,
Cfg: cfg.Orchestrator,
Agg: cfg.Aggregation,
Inbound: cfg.Inbound,
Log: log,
DB: d,
OS: osClient,
Cfg: cfg.Orchestrator,
Agg: cfg.Aggregation,
Log: log,
}
}
@@ -509,10 +509,10 @@ func (o *Orchestrator) aggregateAndRelease(ctx context.Context, item db.IPQueueI
// expectedCheckCount is the number of check rows a fully-reported IP should
// have: one per (egress check-type x target) plus one per (site x inbound
// port/icmp probe). Reads the current check_types/targets/sites from the
// database, so a config change between assignment and aggregation is
// reflected in this specific aggregation (see the "accepted tradeoff" note
// in docs/PLAN_API_CONFIG_MANAGEMENT.md).
// port/icmp probe). Reads the current check_types/targets/sites/inbound
// checks from the database, so a config change between assignment and
// aggregation is reflected in this specific aggregation (see the "accepted
// tradeoff" note in docs/PLAN_API_CONFIG_MANAGEMENT.md).
func (o *Orchestrator) expectedCheckCount(ctx context.Context) (int, error) {
resolved, err := o.DB.ListResolvedCheckTypes(ctx)
if err != nil {
@@ -526,8 +526,12 @@ func (o *Orchestrator) expectedCheckCount(ctx context.Context) (int, error) {
if err != nil {
return 0, err
}
inboundPerSite := len(o.Inbound.Ports)
if o.Inbound.ICMP {
inbound, err := o.DB.GetInboundChecks(ctx)
if err != nil {
return 0, err
}
inboundPerSite := len(inbound.Ports)
if inbound.ICMP {
inboundPerSite++
}
return egress + inboundPerSite*len(sites), nil
@@ -575,3 +575,52 @@ func TestInboundChecksPartialSites(t *testing.T) {
t.Fatalf("expected pass, got %s", ip.OverallResult)
}
}
// TestExpectedCheckCountReflectsInboundChecksConfigChange confirms
// expectedCheckCount reads inbound_checks (ports/icmp) from the database on
// every use, not a cached copy — same "accepted tradeoff" already true of
// check_types/targets/sites (see expectedCheckCount's doc comment).
func TestExpectedCheckCountReflectsInboundChecksConfigChange(t *testing.T) {
ctx := context.Background()
sites := []config.SiteConfig{{SiteID: "site-1", Index: 1}}
o, d, mock := newTestOrchestratorWithSites(t, 180, sites)
// newTestOrchestratorWithSites seeds Inbound: {Ports: [22, 80], ICMP: true}
// (3 inbound checks expected per site).
mock.Seed("fip-1", "1.2.3.4", "svc-project")
_ = d.RegisterValidator(ctx, "validator-1", "host-1", "port-1", "v0.1")
_ = d.SeedQueue(ctx, []string{"1.2.3.4"})
o.Tick(ctx)
ip, _ := d.GetIPByAddress(ctx, "1.2.3.4")
_ = o.SelfCheckResult(ctx, "validator-1", ip.ID, true, "ok")
ip, _ = d.GetIP(ctx, ip.ID)
_ = o.RecordCheck(ctx, db.Check{
IPID: ip.ID, IPAddress: ip.IPAddress, AttemptNumber: ip.AttemptNumber,
ValidatorID: "validator-1", Source: db.SourceEgress, CheckType: "https",
Target: "https://example.test", Success: true, CheckedAt: db.Now(),
})
_ = o.MarkEgressComplete(ctx, ip.ID)
// Shrink the inbound check config to a single TCP port, no ICMP — an
// admin API change made between assignment and aggregation.
if err := d.SetInboundChecks(ctx, []int{22}, false); err != nil {
t.Fatalf("set inbound checks: %v", err)
}
// Report only the one now-expected inbound check.
_ = o.RecordCheck(ctx, db.Check{
IPID: ip.ID, IPAddress: ip.IPAddress, AttemptNumber: ip.AttemptNumber,
Source: db.InboundSource(1), CheckType: "tcp-22", Target: ip.IPAddress, Success: true, CheckedAt: db.Now(),
})
_ = o.MarkSiteComplete(ctx, ip.ID, 1)
o.Tick(ctx)
ip, _ = d.GetIP(ctx, ip.ID)
if ip.State != db.IPDone {
t.Fatalf("expected done once the (shrunk) inbound config was fully reported, got %s", ip.State)
}
if ip.OverallResult != db.ResultPass {
t.Fatalf("expected pass, got %s", ip.OverallResult)
}
}