From cac7d44f87eff758fc70b2832e2359d3ec38b2da Mon Sep 17 00:00:00 2001 From: Annika Hannig Date: Mon, 10 Jan 2022 17:02:44 +0100 Subject: [PATCH] fix load time when cache is not ready with openbgpd --- VERSION | 2 +- pkg/http/api_endpoints_neighbors.go | 7 ++- pkg/sources/birdwatcher/source_multitable.go | 5 ++ pkg/sources/birdwatcher/source_singletable.go | 6 +- pkg/sources/gobgp/source.go | 5 ++ pkg/sources/openbgpd/bgplgd_source.go | 60 +++++++++++++++++-- pkg/sources/openbgpd/state_server_source.go | 58 ++++++++++++++++-- pkg/sources/source.go | 1 + .../backends/memory/neighbors_backend.go | 4 ++ pkg/store/sources_store.go | 13 +++- 10 files changed, 143 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index 6b244dc..831446c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.0.1 +5.1.0 diff --git a/pkg/http/api_endpoints_neighbors.go b/pkg/http/api_endpoints_neighbors.go index 09dbda1..4f8ef17 100644 --- a/pkg/http/api_endpoints_neighbors.go +++ b/pkg/http/api_endpoints_neighbors.go @@ -24,7 +24,10 @@ func (s *Server) apiNeighborsList( var neighborsResponse *api.NeighborsResponse // Try to fetch neighbors from store, only fall back - // to RS query if store is not ready yet + // to RS query if store is not ready yet. + // The stored neighbors response includes details like + // the number of filtered routes which might be lacking + // from the summary. if s.neighborsStore.IsInitialized(rsID) { status, err := s.neighborsStore.GetStatus(rsID) neighbors, err := s.neighborsStore.GetNeighborsAt(ctx, rsID) @@ -51,7 +54,7 @@ func (s *Server) apiNeighborsList( if source == nil { return nil, ErrSourceNotFound } - neighborsResponse, err = source.Neighbors() + neighborsResponse, err = source.NeighborsSummary() if err != nil { s.logSourceError("neighbors", rsID, err) return nil, err diff --git a/pkg/sources/birdwatcher/source_multitable.go b/pkg/sources/birdwatcher/source_multitable.go index 6fc559a..5fdd7dc 100644 --- a/pkg/sources/birdwatcher/source_multitable.go +++ b/pkg/sources/birdwatcher/source_multitable.go @@ -395,6 +395,11 @@ func (src *MultiTableBirdwatcher) Neighbors() (*api.NeighborsResponse, error) { return response, nil // dereference for now } +// NeighborsSummary is for now using Neighbors +func (src *MultiTableBirdwatcher) NeighborsSummary() (*api.NeighborsResponse, error) { + return src.Neighbors() +} + // Routes gets filtered and exported route // from the birdwatcher backend. func (src *MultiTableBirdwatcher) Routes( diff --git a/pkg/sources/birdwatcher/source_singletable.go b/pkg/sources/birdwatcher/source_singletable.go index 3ea89ff..f6a4b3e 100644 --- a/pkg/sources/birdwatcher/source_singletable.go +++ b/pkg/sources/birdwatcher/source_singletable.go @@ -182,10 +182,14 @@ func (src *SingleTableBirdwatcher) Neighbors() (*api.NeighborsResponse, error) { // Cache result src.neighborsCache.Set(response) - return response, nil // dereference for now } +// NeighborsSummary is for now an alias of Neighbors +func (src *SingleTableBirdwatcher) NeighborsSummary() (*api.NeighborsResponse, error) { + return src.Neighbors() +} + // Routes gets filtered and exported routes func (src *SingleTableBirdwatcher) Routes( neighborID string, diff --git a/pkg/sources/gobgp/source.go b/pkg/sources/gobgp/source.go index 50df20d..5ef2b3a 100644 --- a/pkg/sources/gobgp/source.go +++ b/pkg/sources/gobgp/source.go @@ -202,6 +202,11 @@ func (gobgp *GoBGP) Neighbors() (*api.NeighborsResponse, error) { return &response, nil } +// NeighborsSummary is an alias of Neighbors for now +func (gobgp *GoBGP) NeighborsSummary() (*api.NeighborsResponse, error) { + return gobgp.Neighbors() +} + // Routes retrieves filtered and exported routes func (gobgp *GoBGP) Routes(neighborID string) (*api.RoutesResponse, error) { neigh, err := gobgp.lookupNeighbor(neighborID) diff --git a/pkg/sources/openbgpd/bgplgd_source.go b/pkg/sources/openbgpd/bgplgd_source.go index b389c13..bb374e5 100644 --- a/pkg/sources/openbgpd/bgplgd_source.go +++ b/pkg/sources/openbgpd/bgplgd_source.go @@ -23,7 +23,8 @@ type BgplgdSource struct { cfg *Config // Store the neighbor responses from the server here - neighborsCache *caches.NeighborsCache + neighborsCache *caches.NeighborsCache + neighborsSummaryCache *caches.NeighborsCache // Store the routes responses from the server // here identified by neighborID @@ -38,16 +39,18 @@ func NewBgplgdSource(cfg *Config) *BgplgdSource { // Initialize caches nc := caches.NewNeighborsCache(cacheDisabled) + nsc := caches.NewNeighborsCache(cacheDisabled) rc := caches.NewRoutesCache(cacheDisabled, cfg.RoutesCacheSize) rrc := caches.NewRoutesCache(cacheDisabled, cfg.RoutesCacheSize) rfc := caches.NewRoutesCache(cacheDisabled, cfg.RoutesCacheSize) return &BgplgdSource{ - cfg: cfg, - neighborsCache: nc, - routesCache: rc, - routesReceivedCache: rrc, - routesFilteredCache: rfc, + cfg: cfg, + neighborsCache: nc, + neighborsSummaryCache: nsc, + routesCache: rc, + routesReceivedCache: rrc, + routesFilteredCache: rfc, } } @@ -172,6 +175,51 @@ func (src *BgplgdSource) Neighbors() (*api.NeighborsResponse, error) { return response, nil } +// NeighborsSummary retrievs list of neighbors, which +// might lack details like with number of rejected routes. +// It is much faster though. +func (src *BgplgdSource) NeighborsSummary() (*api.NeighborsResponse, error) { + // Query cache and see if we have a hit + response := src.neighborsSummaryCache.Get() + if response != nil { + response.Meta.ResultFromCache = true + return response, nil + } + + // Make API request and read response + req, err := src.ShowNeighborsRequest(context.Background()) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + body, err := decoders.ReadJSONResponse(res) + if err != nil { + return nil, err + } + + nb, err := decodeNeighbors(body) + if err != nil { + return nil, err + } + // Set route server id (sourceID) for all neighbors and + // calculate the filtered routes. + for _, n := range nb { + n.RouteServerID = src.cfg.ID + + } + response = &api.NeighborsResponse{ + Response: api.Response{ + Meta: src.makeResponseMeta(), + }, + Neighbors: nb, + } + src.neighborsSummaryCache.Set(response) + return response, nil +} + // NeighborsStatus retrives the status summary // for all neightbors func (src *BgplgdSource) NeighborsStatus() (*api.NeighborsStatusResponse, error) { diff --git a/pkg/sources/openbgpd/state_server_source.go b/pkg/sources/openbgpd/state_server_source.go index 5672ee2..2d2e86e 100644 --- a/pkg/sources/openbgpd/state_server_source.go +++ b/pkg/sources/openbgpd/state_server_source.go @@ -28,7 +28,8 @@ type StateServerSource struct { cfg *Config // Store the neighbor responses from the server here - neighborsCache *caches.NeighborsCache + neighborsCache *caches.NeighborsCache + neighborsSummaryCache *caches.NeighborsCache // Store the routes responses from the server // here identified by neighborID @@ -44,16 +45,18 @@ func NewStateServerSource(cfg *Config) *StateServerSource { // Initialize caches nc := caches.NewNeighborsCache(cacheDisabled) + nsc := caches.NewNeighborsCache(cacheDisabled) rc := caches.NewRoutesCache(cacheDisabled, cfg.RoutesCacheSize) rrc := caches.NewRoutesCache(cacheDisabled, cfg.RoutesCacheSize) rfc := caches.NewRoutesCache(cacheDisabled, cfg.RoutesCacheSize) return &StateServerSource{ - cfg: cfg, - neighborsCache: nc, - routesCache: rc, - routesReceivedCache: rrc, - routesFilteredCache: rfc, + cfg: cfg, + neighborsCache: nc, + neighborsSummaryCache: nsc, + routesCache: rc, + routesReceivedCache: rrc, + routesFilteredCache: rfc, } } @@ -195,6 +198,49 @@ func (src *StateServerSource) Neighbors() (*api.NeighborsResponse, error) { return response, nil } +// NeighborsSummary retrieves the neighbors without additional +// information but as quickly as possible. The result will lack +// a reject count. +func (src *StateServerSource) NeighborsSummary() (*api.NeighborsResponse, error) { + response := src.neighborsSummaryCache.Get() + if response != nil { + response.Meta.ResultFromCache = true + return response, nil + } + + // Make API request and read response + req, err := src.ShowNeighborsRequest(context.Background()) + if err != nil { + return nil, err + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + body, err := decoders.ReadJSONResponse(res) + if err != nil { + return nil, err + } + + nb, err := decodeNeighbors(body) + if err != nil { + return nil, err + } + // Set route server id (sourceID) for all neighbors + for _, n := range nb { + n.RouteServerID = src.cfg.ID + } + + response = &api.NeighborsResponse{ + Response: api.Response{ + Meta: src.makeResponseMeta(), + }, + Neighbors: nb, + } + src.neighborsSummaryCache.Set(response) + return response, nil +} + // NeighborsStatus retrives the status summary // for all neightbors func (src *StateServerSource) NeighborsStatus() (*api.NeighborsStatusResponse, error) { diff --git a/pkg/sources/source.go b/pkg/sources/source.go index a6935f8..29b1f4f 100644 --- a/pkg/sources/source.go +++ b/pkg/sources/source.go @@ -25,6 +25,7 @@ type Source interface { ExpireCaches() int Status() (*api.StatusResponse, error) Neighbors() (*api.NeighborsResponse, error) + NeighborsSummary() (*api.NeighborsResponse, error) NeighborsStatus() (*api.NeighborsStatusResponse, error) Routes(neighborID string) (*api.RoutesResponse, error) RoutesReceived(neighborID string) (*api.RoutesResponse, error) diff --git a/pkg/store/backends/memory/neighbors_backend.go b/pkg/store/backends/memory/neighbors_backend.go index d387deb..009a3a2 100644 --- a/pkg/store/backends/memory/neighbors_backend.go +++ b/pkg/store/backends/memory/neighbors_backend.go @@ -2,6 +2,7 @@ package memory import ( "context" + "errors" "sync" "github.com/alice-lg/alice-lg/pkg/api" @@ -97,6 +98,9 @@ func (b *NeighborsBackend) CountNeighborsAt( ) (int, error) { neighbors, err := b.GetNeighborsAt(ctx, sourceID) if err != nil { + if errors.Is(err, sources.ErrSourceNotFound) { + return 0, nil + } return 0, err } return len(neighbors), nil diff --git a/pkg/store/sources_store.go b/pkg/store/sources_store.go index d1c6bed..6eb2501 100644 --- a/pkg/store/sources_store.go +++ b/pkg/store/sources_store.go @@ -141,14 +141,23 @@ func (s *SourcesStore) ShouldRefresh( log.Println("get status error:", err) return false } + + nextRefresh := status.LastRefresh.Add(s.refreshInterval) + if status.State == StateBusy { return false // Source is busy } - nextRefresh := status.LastRefresh.Add( - s.refreshInterval) + if status.State == StateError { + // The refresh interval in the config is ok if the + // success case. When an error occures it is desireable + // to retry sooner, without spamming the server. + nextRefresh = status.LastRefresh.Add(10 * time.Second) + } + if time.Now().UTC().Before(nextRefresh) { return false // Too soon } + return true // Go for it }