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
@@ -0,0 +1,109 @@
|
||||
// Package agentwatch reloads BIRD when its active configuration tree changes.
|
||||
package agentwatch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Control is the subset of BIRD operations required by the watch loop.
|
||||
type Control interface {
|
||||
ParseCheck(context.Context, string) error
|
||||
Configure(context.Context) error
|
||||
}
|
||||
|
||||
// Config sets bounded polling and reconciliation behavior.
|
||||
type Config struct {
|
||||
ActiveDir string
|
||||
Every time.Duration
|
||||
ReconcileEvery time.Duration
|
||||
CommandTimeout time.Duration
|
||||
Logf func(string, ...any)
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
ctl Control
|
||||
cfg Config
|
||||
hash [sha256.Size]byte
|
||||
haveHash bool
|
||||
lastConfigured time.Time
|
||||
}
|
||||
|
||||
// Run starts with one check, then polls until ctx is cancelled. A successful
|
||||
// birdc exit advances the remembered hash; it is not proof of BIRD ACK.
|
||||
func Run(ctx context.Context, ctl Control, cfg Config) error {
|
||||
if ctl == nil || cfg.Every <= 0 || cfg.ReconcileEvery <= 0 || cfg.CommandTimeout <= 0 {
|
||||
return fmt.Errorf("agentwatch: control and positive intervals/timeouts are required")
|
||||
}
|
||||
if cfg.ActiveDir == "" {
|
||||
cfg.ActiveDir = "/etc/bird"
|
||||
}
|
||||
if cfg.Logf == nil {
|
||||
cfg.Logf = log.Printf
|
||||
}
|
||||
w := &watcher{ctl: ctl, cfg: cfg}
|
||||
if err := w.step(ctx, time.Now()); err != nil && ctx.Err() == nil {
|
||||
cfg.Logf("evobgp-agent watch: %v", err)
|
||||
}
|
||||
ticker := time.NewTicker(cfg.Every)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case now := <-ticker.C:
|
||||
if err := w.step(ctx, now); err != nil && ctx.Err() == nil {
|
||||
cfg.Logf("evobgp-agent watch: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *watcher) step(ctx context.Context, now time.Time) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
digest, scanErr := hashConfigTree(ctx, w.cfg.ActiveDir)
|
||||
if scanErr != nil {
|
||||
if errors.Is(scanErr, errMainNotReady) {
|
||||
return fmt.Errorf("configuration not ready: %w", scanErr)
|
||||
}
|
||||
// Unsupported/unhashable trees retain the old every-tick configure cadence.
|
||||
w.cfg.Logf("evobgp-agent watch: cannot hash complete config tree (%v); using every-tick parse/configure", scanErr)
|
||||
} else if w.haveHash && digest == w.hash && now.Sub(w.lastConfigured) < w.cfg.ReconcileEvery {
|
||||
return nil
|
||||
}
|
||||
main := filepath.Join(w.cfg.ActiveDir, "bird.conf")
|
||||
if err := w.command(ctx, func(cctx context.Context) error { return w.ctl.ParseCheck(cctx, main) }); err != nil {
|
||||
return fmt.Errorf("parse check: %w", err)
|
||||
}
|
||||
if scanErr == nil {
|
||||
stable, err := hashConfigTree(ctx, w.cfg.ActiveDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("configuration changed during parse check: %w", err)
|
||||
}
|
||||
if stable != digest {
|
||||
return fmt.Errorf("configuration changed during parse check; retrying")
|
||||
}
|
||||
}
|
||||
if err := w.command(ctx, w.ctl.Configure); err != nil {
|
||||
return fmt.Errorf("configure: %w", err)
|
||||
}
|
||||
w.haveHash = scanErr == nil
|
||||
if w.haveHash {
|
||||
w.hash = digest
|
||||
}
|
||||
w.lastConfigured = now
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *watcher) command(parent context.Context, run func(context.Context) error) error {
|
||||
ctx, cancel := context.WithTimeout(parent, w.cfg.CommandTimeout)
|
||||
defer cancel()
|
||||
return run(ctx)
|
||||
}
|
||||
Reference in new issue
Block a user