feat(pipeline): use stale snapshot when upstream fetch fails

При ошибке CDN/ASN/DoH ingest использует последний снимок префиксов
(или просроченный ASN-кэш), если EVOBGP_STALE_ON_UPSTREAM_ERROR не равен 0.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-05-25 10:13:07 +07:00
co-authored by Cursor
parent 9639a03bfe
commit 6a6f6cedbc
4 changed files with 215 additions and 4 deletions
+57 -2
View File
@@ -25,7 +25,7 @@ func prefixRowsForSource(rows []store.PrefixRow, sourceKey string) []store.Prefi
return out
}
func collectASPrefixRows(ctx context.Context, st store.Backend, hc *http.Client, tenantID string, mod *store.Module, list []*store.ASEntry) ([]store.PrefixRow, error) {
func collectASPrefixRows(ctx context.Context, st store.Backend, hc *http.Client, tenantID string, mod *store.Module, list []*store.ASEntry, priorSnapshot []store.PrefixRow) ([]store.PrefixRow, error) {
moduleID := mod.ID
legacy := strings.TrimSpace(os.Getenv("EVOBGP_ASN_RESOLVE")) == "0"
if legacy {
@@ -76,6 +76,41 @@ func collectASPrefixRows(ctx context.Context, st store.Backend, hc *http.Client,
}
pfxs, holder, err := resolveASNForEntry(ctx, st, hc, entry.ASN)
if err != nil {
if staleOnUpstreamError() {
if staleRows, staleHolder, ok := staleASNPrefixes(st, priorSnapshot, entry.ASN); ok {
logStaleUpstream("asn", fmt.Sprintf("AS%d: %v", entry.ASN, err))
src := fmt.Sprintf("as:%d", entry.ASN)
rows := append([]store.PrefixRow(nil), staleRows...)
for i := range rows {
rows[i].CommunityID = comm
rows[i].Source = src
}
results[idx] = entryResult{
rows: rows,
metaID: entry.ID,
asn: entry.ASN,
holder: staleHolder,
count: int64(len(rows)),
}
return
}
if pfxs2, holder2, ok := asnCacheExpired(st, entry.ASN); ok {
logStaleUpstream("asn", fmt.Sprintf("AS%d expired cache: %v", entry.ASN, err))
src := fmt.Sprintf("as:%d", entry.ASN)
var rows []store.PrefixRow
for _, pfx := range pfxs2 {
rows = append(rows, store.PrefixRow{Prefix: pfx.String(), CommunityID: comm, Source: src})
}
results[idx] = entryResult{
rows: rows,
metaID: entry.ID,
asn: entry.ASN,
holder: holder2,
count: int64(len(pfxs2)),
}
return
}
}
results[idx] = entryResult{err: fmt.Errorf("resolve AS%d: %w", entry.ASN, err)}
return
}
@@ -167,6 +202,13 @@ func collectCDNPrefixRows(ctx context.Context, st store.Backend, hc *http.Client
}
rows, err := fetchCDNSourceRows(ctx, st, hc, tenantID, moduleID, mod, src, priorSnapshot, now)
if err != nil {
if staleOnUpstreamError() {
if cached, ok := staleCDNPrefixes(st, tenantID, moduleID, priorSnapshot, src.ID); ok {
logStaleUpstream("cdn", fmt.Sprintf("source %s: %v", src.ID, err))
results[idx] = srcResult{rows: cached}
return
}
}
results[idx] = srcResult{err: err}
return
}
@@ -190,7 +232,7 @@ func collectCDNPrefixRows(ctx context.Context, st store.Backend, hc *http.Client
return out, nil
}
func collectDomainPrefixRows(ctx context.Context, hc *http.Client, mod *store.Module, profiles []*store.DohProfile, policy string, entries []*store.DomainEntry) ([]store.PrefixRow, error) {
func collectDomainPrefixRows(ctx context.Context, hc *http.Client, mod *store.Module, profiles []*store.DohProfile, policy string, entries []*store.DomainEntry, priorSnapshot []store.PrefixRow) ([]store.PrefixRow, error) {
var validDom []*store.DomainEntry
for _, e := range entries {
if e != nil {
@@ -219,6 +261,19 @@ func collectDomainPrefixRows(ctx context.Context, hc *http.Client, mod *store.Mo
}
addrs, err := resolveDomainIPsWithPolicy(ctx, hc, profiles, policy, entry.FQDN)
if err != nil {
if staleOnUpstreamError() {
if cached, ok := staleDomainPrefixes(priorSnapshot, entry.FQDN); ok {
logStaleUpstream("domain", fmt.Sprintf("%q: %v", entry.FQDN, err))
rows := append([]store.PrefixRow(nil), cached...)
for i := range rows {
if rows[i].CommunityID == nil {
rows[i].CommunityID = comm
}
}
results[idx] = domResult{rows: rows}
return
}
}
results[idx] = domResult{err: fmt.Errorf("resolve domain %q: %w", entry.FQDN, err)}
return
}