feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
924f111e48
commit
f8e0520817
197 files changed
+18655
-1416
No files matched your search
@@ -8,15 +8,17 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"evobgp/internal/audit"
|
||||
"evobgp/internal/jobs"
|
||||
"evobgp/internal/logging"
|
||||
"evobgp/internal/maintenance"
|
||||
"evobgp/internal/pgmonitor"
|
||||
"evobgp/internal/repository"
|
||||
"evobgp/internal/runtimelogs"
|
||||
"evobgp/internal/store"
|
||||
"evobgp/internal/telemetry"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
@@ -30,7 +32,11 @@ type Server struct {
|
||||
maintStats *maintenance.DBStatsProvider
|
||||
jobs *jobs.Registry
|
||||
authLimiter *authRateLimiter
|
||||
operationOnce sync.Once
|
||||
operationLimiter *operationLimiter
|
||||
bundlePriv ed25519.PrivateKey
|
||||
bundleCache *nodeBundleCache
|
||||
nodeBundleBuild nodeBundleBuilder
|
||||
keyResolver *apiKeyResolver
|
||||
firewallResolver *firewallTokenResolver
|
||||
bundleSeedHex string
|
||||
@@ -39,6 +45,11 @@ type Server struct {
|
||||
runtimeLogs *runtimelogs.Service
|
||||
runtimeLogsPolicyTenant string
|
||||
mux *http.ServeMux
|
||||
backgroundMu sync.Mutex
|
||||
backgroundStarted bool
|
||||
backgroundClosed bool
|
||||
backgroundCancel context.CancelFunc
|
||||
telemetryCollector *telemetry.Collector
|
||||
|
||||
// Portal / dual-auth (JWT) configuration.
|
||||
jwtSecret string
|
||||
@@ -118,6 +129,7 @@ func New(opts Options) (*Server, error) {
|
||||
maintStats: maintStats,
|
||||
jobs: reg,
|
||||
bundlePriv: priv,
|
||||
bundleCache: newNodeBundleCache(nodeBundleCacheMaxEntries, nodeBundleCacheMaxBytes),
|
||||
keyResolver: resolver,
|
||||
firewallResolver: fwResolver,
|
||||
bundleSeedHex: strings.TrimSpace(opts.BundleSeedHex),
|
||||
@@ -143,6 +155,29 @@ func New(opts Options) (*Server, error) {
|
||||
|
||||
// Close releases database resources.
|
||||
func (s *Server) Close() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.backgroundMu.Lock()
|
||||
if s.backgroundClosed {
|
||||
s.backgroundMu.Unlock()
|
||||
return
|
||||
}
|
||||
s.backgroundClosed = true
|
||||
cancel := s.backgroundCancel
|
||||
collector := s.telemetryCollector
|
||||
s.backgroundMu.Unlock()
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
if collector != nil {
|
||||
_ = collector.Close(context.Background())
|
||||
}
|
||||
// Do not close the pool while a worker is still persisting its final state.
|
||||
if s.jobs != nil {
|
||||
s.jobs.RequestCancelAll()
|
||||
_ = s.jobs.Drain(context.Background())
|
||||
}
|
||||
if s.pgPool != nil {
|
||||
s.pgPool.Close()
|
||||
}
|
||||
@@ -154,26 +189,44 @@ func (s *Server) Store() store.Backend { return s.store }
|
||||
// Jobs exposes the in-process async job registry (for scheduler / evobgp-all).
|
||||
func (s *Server) Jobs() *jobs.Registry { return s.jobs }
|
||||
|
||||
// StartBackground starts PostgreSQL monitoring and maintenance schedulers until ctx is cancelled.
|
||||
// StartBackground starts shared schedulers and telemetry polling once until ctx is cancelled.
|
||||
func (s *Server) StartBackground(ctx context.Context) {
|
||||
if s != nil && s.pgPool != nil {
|
||||
pgmonitor.StartScheduler(ctx, s.pgPool)
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.backgroundMu.Lock()
|
||||
defer s.backgroundMu.Unlock()
|
||||
if s.backgroundClosed || s.backgroundStarted {
|
||||
return
|
||||
}
|
||||
s.backgroundStarted = true
|
||||
backgroundCtx, cancel := context.WithCancel(ctx)
|
||||
s.backgroundCancel = cancel
|
||||
if s.store != nil {
|
||||
s.telemetryCollector = telemetry.Start(backgroundCtx, s.store, telemetry.ConfigFromEnv())
|
||||
}
|
||||
if s.pgPool != nil {
|
||||
pgmonitor.StartScheduler(backgroundCtx, s.pgPool)
|
||||
if s.jobs != nil {
|
||||
jobs.StartDurableQueueLoop(ctx, s.jobs, repository.NewJobAuditWriter(s.pgPool))
|
||||
jobs.StartDurableQueueLoop(backgroundCtx, s.jobs)
|
||||
jobs.StartDeploymentReconciliationLoop(backgroundCtx, s.jobs, s.store)
|
||||
}
|
||||
}
|
||||
if s != nil && s.maintConfig != nil && s.jobs != nil {
|
||||
maintenance.StartScheduler(ctx, s.maintConfig, func(policyID string, dryRun bool, idem string) {
|
||||
maintenance.StartScheduler(backgroundCtx, s.maintConfig, func(policyID string, dryRun bool, idem string) {
|
||||
key := idem
|
||||
_, _, _ = s.jobs.Enqueue("", jobs.KindMaintenancePolicyRun, &key, nil, map[string]any{
|
||||
_, _, err := s.jobs.EnqueueContext(backgroundCtx, store.SystemJobTenantID, jobs.KindMaintenancePolicyRun, &key, nil, map[string]any{
|
||||
"policy_id": policyID,
|
||||
"dry_run": dryRun,
|
||||
"trigger": "scheduler",
|
||||
})
|
||||
if err != nil {
|
||||
logging.With("component", "maintenance.scheduler").Error("enqueue failed", "policy_id", policyID, "err", err)
|
||||
}
|
||||
}, 30*time.Second)
|
||||
}
|
||||
if s != nil && s.runtimeLogs != nil && s.store != nil {
|
||||
runtimelogs.StartAutoCleanupScheduler(ctx, runtimelogs.SchedulerDeps{
|
||||
runtimelogs.StartAutoCleanupScheduler(backgroundCtx, runtimelogs.SchedulerDeps{
|
||||
Service: s.runtimeLogs,
|
||||
Store: s.store,
|
||||
PolicyTenant: s.runtimeLogsPolicyTenant,
|
||||
|
||||
Reference in new issue
Block a user