Files
OpenFlare/internal/apps/agent/sync/sync_helpers.go
T
ryan 65c02ef7a5 基准扩展 exhaustive(文档化)+ 12→0:枚举 switch 补显式 case(全部与现有 default 行为等价,fail-explicit 防未来枚举静默落入 default);source_tasks.go 为控制复杂度合并两个等价校验条件。
Result: {"status":"keep","total_issues":8,"golint_canonicalheader":0,"golint_errname":0,"golint_errorlint":1,"golint_exhaustive":0,"golint_forcetypeassert":0,"golint_gosec":0,"golint_intrange":0,"golint_modernize":3,"golint_nilnil":3,"golint_perfsprint":0,"golint_prealloc":0,"golint_recvcheck":1,"golint_usestdlibvars":0,"golint_wastedassign":0,"golint_total":8,"golint_test_testifylint":0,"golint_test_thelper":0,"golint_test_usetesting":0,"golint_test_total":0,"eslint_problems":0,"eslint_errors":0,"eslint_warnings":0,"tsc_errors":0,"measure_s":36}
2026-08-16 18:19:08 +08:00

253 lines
8.9 KiB
Go

package sync
import (
"context"
"log/slog"
"strings"
"github.com/Rain-kl/Wavelet/internal/apps/agent/nginx"
"github.com/Rain-kl/Wavelet/internal/apps/agent/protocol"
"github.com/Rain-kl/Wavelet/internal/apps/agent/state"
)
func syncMode(startup bool) string {
if startup {
return "startup"
}
return "periodic"
}
func normalizeSyncTarget(target *protocol.ActiveConfigMeta) {
if target == nil {
return
}
target.Version = strings.TrimSpace(target.Version)
target.Checksum = strings.TrimSpace(target.Checksum)
}
func (s *Service) loadSyncState() (*state.Snapshot, string, error) {
snapshot, err := s.stateStore.Load()
if err != nil {
return nil, "", err
}
currentChecksum, err := s.nginxManager.CurrentChecksum()
if err != nil {
return nil, "", err
}
return snapshot, currentChecksum, nil
}
func (s *Service) syncWithoutTarget(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string) error {
if !startup {
slog.Debug("skipping sync because heartbeat returned no active config summary", "mode", mode)
return nil
}
slog.Debug("sync startup fallback: active config summary unavailable, fetching active config directly")
config, fetchErr := s.client.GetActiveConfig(ctx)
if fetchErr != nil {
slog.Error("fetch active config failed", "mode", mode, "error", fetchErr)
return fetchErr
}
target := &protocol.ActiveConfigMeta{
Version: config.Version,
Checksum: config.Checksum,
}
return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config)
}
func (s *Service) syncMatchingChecksum(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta) error {
if startup {
config, fetchErr := s.client.GetActiveConfig(ctx)
if fetchErr != nil {
slog.Error("fetch active config failed", "mode", mode, "error", fetchErr)
return fetchErr
}
return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config)
}
return s.finishUpToDateSync(ctx, mode, snapshot, target)
}
func (s *Service) finishUpToDateSync(ctx context.Context, mode string, snapshot *state.Snapshot, target *protocol.ActiveConfigMeta) error {
if err := s.reconcilePages(ctx, mode, snapshot); err != nil {
return err
}
slog.Debug("local openresty config already up to date", "mode", mode, "version", target.Version)
if shouldReportNoopApply(snapshot, target.Version, target.Checksum) {
if err := s.reportNoopApply(ctx, snapshot.NodeID, target.Version, target.Checksum, "", "", 0); err != nil {
return err
}
}
snapshot.CurrentVersion = target.Version
snapshot.CurrentChecksum = target.Checksum
clearBlockedTarget(snapshot)
snapshot.LastError = ""
slog.Debug("sync finished without changes", "mode", mode, "version", target.Version)
return s.stateStore.Save(snapshot)
}
func (s *Service) reconcilePages(ctx context.Context, mode string, snapshot *state.Snapshot) error {
if !pagesReconcileNeeded(snapshot) {
return nil
}
if pagesDiscoveryNeeded(snapshot) {
config, err := s.client.GetActiveConfig(ctx)
if err != nil {
slog.Error("fetch active config failed", "mode", mode, "error", err)
return err
}
return s.syncPagesDeployments(ctx, snapshot, config)
}
return s.syncPagesDeployments(ctx, snapshot, nil)
}
func (s *Service) syncMismatchedChecksum(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, target *protocol.ActiveConfigMeta) error {
if isBlockedTarget(snapshot, target.Version, target.Checksum) {
slog.Warn("skipping blocked config version after previous failed apply", "mode", mode, "version", target.Version, "checksum", target.Checksum)
if startup {
if err := s.ensureRuntimeForCurrentConfig(ctx, mode, snapshot, currentChecksum); err != nil {
return err
}
return s.stateStore.Save(snapshot)
}
return nil
}
if hasBlockedTarget(snapshot) {
clearBlockedTarget(snapshot)
}
if snapshot.CurrentVersion == target.Version && snapshot.CurrentChecksum == target.Checksum && !startup {
reconciled := pagesReconcileNeeded(snapshot)
if err := s.reconcilePages(ctx, mode, snapshot); err != nil {
return err
}
if !reconciled {
slog.Debug("skipping config fetch because state already records target version/checksum", "version", target.Version, "checksum", target.Checksum)
}
// Successful reconcile must clear sticky LastError so health events can resolve.
snapshot.LastError = ""
return s.stateStore.Save(snapshot)
}
config, err := s.client.GetActiveConfig(ctx)
if err != nil {
slog.Error("fetch active config failed", "mode", mode, "error", err)
return err
}
return s.applyIfNeeded(ctx, mode, startup, snapshot, currentChecksum, target, config)
}
func (s *Service) handleUpToDateConfig(ctx context.Context, mode string, snapshot *state.Snapshot, config *protocol.ActiveConfigResponse) error {
if err := s.syncPagesDeployments(ctx, snapshot, config); err != nil {
return err
}
slog.Debug("local openresty config already up to date", "mode", mode, "version", config.Version)
if shouldReportNoopApply(snapshot, config.Version, config.Checksum) {
rendered, renderErr := renderActiveConfig(config)
if renderErr != nil {
return renderErr
}
if err := s.reportNoopApply(
ctx,
snapshot.NodeID,
config.Version,
config.Checksum,
checksumString(rendered.mainConfig),
checksumString(rendered.routeConfig),
len(rendered.supportFiles),
); err != nil {
return err
}
}
snapshot.CurrentVersion = config.Version
snapshot.CurrentChecksum = config.Checksum
clearBlockedTarget(snapshot)
snapshot.LastError = ""
slog.Debug("sync finished without changes", "mode", mode, "version", config.Version)
return s.stateStore.Save(snapshot)
}
func (s *Service) handleBlockedConfigAfterFetch(ctx context.Context, mode string, startup bool, snapshot *state.Snapshot, currentChecksum string, config *protocol.ActiveConfigResponse) (bool, error) {
if !isBlockedTarget(snapshot, config.Version, config.Checksum) {
return false, nil
}
slog.Warn("skipping blocked config after fetch because the same version previously failed", "mode", mode, "version", config.Version, "checksum", config.Checksum)
if startup {
if err := s.ensureRuntimeForCurrentConfig(ctx, mode, snapshot, currentChecksum); err != nil {
return true, err
}
return true, s.stateStore.Save(snapshot)
}
return true, nil
}
type applyOutcomeResult struct {
reportResult string
message string
}
func normalizeApplyOutcome(outcome nginx.ApplyOutcome) (nginx.ApplyOutcome, string) {
message := strings.TrimSpace(outcome.Message)
if outcome.Status == "" {
outcome.Status = nginx.ApplyStatusFatal
if message == "" {
message = "openresty apply returned empty outcome"
}
}
return outcome, message
}
func updateSnapshotFromApplyOutcome(mode string, snapshot *state.Snapshot, config *protocol.ActiveConfigResponse, outcome nginx.ApplyOutcome, message string) applyOutcomeResult {
result := applyOutcomeResult{reportResult: ApplyResultFailed, message: message}
switch outcome.Status {
case nginx.ApplyStatusSuccess:
slog.Info("openresty config applied successfully", "mode", mode, "version", config.Version)
snapshot.CurrentVersion = config.Version
snapshot.CurrentChecksum = config.Checksum
clearBlockedTarget(snapshot)
snapshot.LastError = ""
snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy
snapshot.OpenrestyMessage = ""
result.reportResult = ApplyResultSuccess
if result.message == "" {
result.message = "apply success"
}
case nginx.ApplyStatusWarning:
if result.message == "" {
result.message = "apply rolled back to previous config"
}
slog.Warn("openresty config apply rolled back", "mode", mode, "version", config.Version, "message", result.message)
markBlockedTarget(snapshot, config.Version, config.Checksum, result.message)
snapshot.LastError = result.message
snapshot.OpenrestyStatus = protocol.OpenrestyStatusHealthy
snapshot.OpenrestyMessage = result.message
result.reportResult = ApplyResultWarning
case nginx.ApplyStatusFatal:
// 致命错误与普通失败同走失败路径:标记阻塞并上报 Unhealthy。
fallthrough
default:
if result.message == "" {
result.message = "openresty apply failed"
}
slog.Error("apply openresty config failed", "mode", mode, "version", config.Version, "message", result.message)
markBlockedTarget(snapshot, config.Version, config.Checksum, result.message)
snapshot.LastError = result.message
snapshot.OpenrestyStatus = protocol.OpenrestyStatusUnhealthy
snapshot.OpenrestyMessage = result.message
}
return result
}
func snapshotMatchesTarget(snapshot *state.Snapshot, version string, checksum string) bool {
if snapshot == nil {
return false
}
return strings.TrimSpace(snapshot.CurrentVersion) == strings.TrimSpace(version) &&
strings.TrimSpace(snapshot.CurrentChecksum) == strings.TrimSpace(checksum)
}
func shouldReportApplyLog(alreadySynced bool, result string) bool {
if result != ApplyResultSuccess {
return true
}
return !alreadySynced
}