quality / commitlint (push) Skipped
quality / changes (push) Successful in 9s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 46s
quality / web (push) Successful in 1m16s
quality / go (push) Successful in 2m42s
quality / bird2 (push) Successful in 16s
CD / quality (push) Successful in 5m19s
CD / publish (push) Successful in 7m19s
- Deleted unused components: `DashboardActivityTimeline`, `DashboardFramePanel`, `DashboardModulesGrid`, `DashboardRecentJobsGrid`, and `DashboardRecentRevisionsGrid` to streamline the dashboard. - Updated `DashboardKpiGrid` to improve KPI display logic, including progress indicators and enhanced badge functionality. - Refactored `DashboardNetworkHealth` to provide better status representation based on loading states and network conditions. - Introduced new properties for KPI cards to support progress tracking and improved visual feedback. This cleanup aims to enhance performance and maintainability of the dashboard while providing a better user experience.
127 lines
3.5 KiB
Go
127 lines
3.5 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/netip"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"evobgp/internal/asnresolve"
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func asnCacheTTL() time.Duration {
|
|
sec := 1800
|
|
if s := strings.TrimSpace(os.Getenv("EVOBGP_ASN_CACHE_TTL_SEC")); s != "" {
|
|
if v, err := strconv.Atoi(s); err == nil && v > 0 {
|
|
sec = v
|
|
}
|
|
}
|
|
return time.Duration(sec) * time.Second
|
|
}
|
|
|
|
// asnHolderTTL is how long a holder name stays authoritative between refreshes;
|
|
// holder text changes rarely, so it survives short prefix-cache TTLs.
|
|
func asnHolderTTL() time.Duration {
|
|
sec := 7 * 24 * 3600
|
|
if s := strings.TrimSpace(os.Getenv("EVOBGP_ASN_HOLDER_TTL_SEC")); s != "" {
|
|
if v, err := strconv.Atoi(s); err == nil && v > 0 {
|
|
sec = v
|
|
}
|
|
}
|
|
return time.Duration(sec) * time.Second
|
|
}
|
|
|
|
// resolveASNForEntry fetches prefixes and holder with shared TTL cache (asn_prefix_cache).
|
|
// The holder lookup starts concurrently with the prefix fetch (one RTT instead of two);
|
|
// a holder failure is non-fatal — the previously cached holder name is kept.
|
|
func resolveASNForEntry(ctx context.Context, st store.Backend, hc *http.Client, asn int64) ([]netip.Prefix, string, error) {
|
|
ttl := asnCacheTTL()
|
|
var prevHolder string
|
|
if st != nil {
|
|
if ent, ok, err := st.GetASNPrefixCache(asn); err == nil && ok && ent != nil {
|
|
prevHolder = ent.Holder
|
|
if time.Since(ent.FetchedAt) < ttl {
|
|
return parseASNCachePrefixes(ent.Prefixes), ent.Holder, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
holderCh := startASNHolderFetch(ctx, hc, st, asn, prevHolder)
|
|
|
|
pfxs, err := asnresolve.AnnouncedPrefixes(ctx, hc, asn)
|
|
if err != nil {
|
|
holder := <-holderCh // drain to avoid leaking the goroutine's channel send
|
|
_ = holder
|
|
return nil, "", err
|
|
}
|
|
|
|
holder := <-holderCh
|
|
if st != nil {
|
|
strs := make([]string, len(pfxs))
|
|
for i, p := range pfxs {
|
|
strs[i] = p.String()
|
|
}
|
|
if err := st.SetASNPrefixCache(asn, holder, strs); err != nil {
|
|
return nil, "", fmt.Errorf("asn cache AS%d: %w", asn, err)
|
|
}
|
|
}
|
|
return pfxs, holder, nil
|
|
}
|
|
|
|
// startASNHolderFetch launches the holder lookup concurrently. The returned buffered
|
|
// channel always yields exactly one value, so callers may abandon it without leaking.
|
|
func startASNHolderFetch(ctx context.Context, hc *http.Client, st store.Backend, asn int64, prevHolder string) <-chan string {
|
|
ch := make(chan string, 1)
|
|
go func() {
|
|
if prevHolder != "" && holderStillFresh(st, asn, prevHolder) {
|
|
ch <- prevHolder
|
|
return
|
|
}
|
|
holder, err := asnresolve.ASHolderName(ctx, hc, asn)
|
|
if err != nil || strings.TrimSpace(holder) == "" {
|
|
ch <- prevHolder
|
|
return
|
|
}
|
|
ch <- strings.TrimSpace(holder)
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
// holderStillFresh reports whether the cached holder name is within its own (long) TTL.
|
|
func holderStillFresh(st store.Backend, asn int64, prevHolder string) bool {
|
|
if st == nil {
|
|
return false
|
|
}
|
|
ent, ok, err := st.GetASNPrefixCache(asn)
|
|
return err == nil && ok && ent != nil && ent.Holder == prevHolder && time.Since(ent.FetchedAt) < asnHolderTTL()
|
|
}
|
|
|
|
func parseASNCachePrefixes(raw []string) []netip.Prefix {
|
|
out := make([]netip.Prefix, 0, len(raw))
|
|
for _, p := range raw {
|
|
pfx, perr := netip.ParsePrefix(strings.TrimSpace(p))
|
|
if perr != nil {
|
|
continue
|
|
}
|
|
out = append(out, pfx.Masked())
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseCachedASNCachedPrefixes(raw []string) []netip.Prefix {
|
|
out := make([]netip.Prefix, 0, len(raw))
|
|
for _, p := range raw {
|
|
pfx, perr := netip.ParsePrefix(strings.TrimSpace(p))
|
|
if perr != nil {
|
|
continue
|
|
}
|
|
out = append(out, pfx.Masked())
|
|
}
|
|
return out
|
|
}
|