Implemented support to query the live neighbor status from the store

via uncached queries.
This commit is contained in:
Patrick Seeburger 2019-03-04 18:52:49 +01:00 committed by Benedikt Rudolph
parent 5175ccfe4c
commit 8f19408bad
9 changed files with 180 additions and 10 deletions

View File

@ -52,3 +52,30 @@ func (self *NeighboursResponse) CacheTtl() time.Duration {
}
type NeighboursLookupResults map[string]Neighbours
type NeighboursStatus []*NeighbourStatus
type NeighbourStatus struct {
Id string `json:"id"`
State string `json:"state"`
Since time.Duration `json:"uptime"`
}
// Implement sorting interface for status
func (neighbours NeighboursStatus) Len() int {
return len(neighbours)
}
func (neighbours NeighboursStatus) Less(i, j int) bool {
return neighbours[i].Id < neighbours[j].Id
}
func (neighbours NeighboursStatus) Swap(i, j int) {
neighbours[i], neighbours[j] = neighbours[j], neighbours[i]
}
type NeighboursStatusResponse struct {
Api ApiStatus `json:"api"`
Neighbours NeighboursStatus `json:"neighbours"`
}

View File

@ -21,6 +21,7 @@ type ServerConfig struct {
NeighboursStoreRefreshInterval int `ini:"neighbours_store_refresh_interval"`
RoutesStoreRefreshInterval int `ini:"routes_store_refresh_interval"`
Asn int `ini:"asn"`
EnableNeighborsStatusRefresh bool `ini:"enable_neighbors_status_refresh"`
}
type HousekeepingConfig struct {

View File

@ -15,10 +15,11 @@ var REGEX_MATCH_ASLOOKUP = regexp.MustCompile(`(?i)^AS(\d+)`)
type NeighboursIndex map[string]*api.Neighbour
type NeighboursStore struct {
neighboursMap map[string]NeighboursIndex
configMap map[string]*SourceConfig
statusMap map[string]StoreStatus
refreshInterval time.Duration
neighboursMap map[string]NeighboursIndex
configMap map[string]*SourceConfig
statusMap map[string]StoreStatus
refreshInterval time.Duration
refreshNeighborStatus bool
sync.RWMutex
}
@ -48,11 +49,14 @@ func NewNeighboursStore(config *Config) *NeighboursStore {
refreshInterval = time.Duration(5) * time.Minute
}
refreshNeighborStatus := config.Server.EnableNeighborsStatusRefresh
store := &NeighboursStore{
neighboursMap: neighboursMap,
statusMap: statusMap,
configMap: configMap,
refreshInterval: refreshInterval,
refreshNeighborStatus: refreshNeighborStatus,
}
return store
}
@ -165,9 +169,32 @@ func (self *NeighboursStore) GetNeighborsAt(sourceId string) api.Neighbours {
neighborsIdx := self.neighboursMap[sourceId]
self.RUnlock()
var neighborsStatus map[string]api.NeighbourStatus
if self.refreshNeighborStatus {
sourceConfig := self.configMap[sourceId]
source := sourceConfig.getInstance()
neighborsStatusData, err := source.NeighboursStatus()
if err == nil {
neighborsStatus = make(map[string]api.NeighbourStatus, len(neighborsStatusData.Neighbours))
for _, neighbor := range neighborsStatusData.Neighbours {
neighborsStatus[neighbor.Id] = *neighbor
}
}
}
neighbors := make(api.Neighbours, 0, len(neighborsIdx))
for _, neighbor := range neighborsIdx {
if self.refreshNeighborStatus {
if _, ok := neighborsStatus[neighbor.Id]; ok {
self.Lock()
neighbor.State = neighborsStatus[neighbor.Id].State
self.Unlock()
}
}
neighbors = append(neighbors, neighbor)
}

View File

@ -6,6 +6,7 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
)
type ClientResponse map[string]interface{}
@ -22,8 +23,8 @@ func NewClient(api string) *Client {
}
// Make API request, parse response and return map or error
func (self *Client) GetJson(endpoint string) (ClientResponse, error) {
res, err := http.Get(self.Api + endpoint)
func (self *Client) Get(client *http.Client, url string) (ClientResponse, error) {
res, err := client.Get(url)
if err != nil {
return ClientResponse{}, err
}
@ -44,3 +45,19 @@ func (self *Client) GetJson(endpoint string) (ClientResponse, error) {
return result, nil
}
// Make API request, parse response and return map or error
func (self *Client) GetJson(endpoint string) (ClientResponse, error) {
client := &http.Client{}
return self.Get(client, self.Api + endpoint)
}
// Make API request, parse response and return map or error
func (self *Client) GetJsonTimeout(timeout time.Duration, endpoint string) (ClientResponse, error) {
client := &http.Client{
Timeout: timeout,
}
return self.Get(client, self.Api + endpoint)
}

View File

@ -11,7 +11,8 @@ type Config struct {
ServerTimeExt string `ini:"servertime_ext"`
ShowLastReboot bool `ini:"show_last_reboot"`
Type string `ini:"type"`
PeerTablePrefix string `ini:"peer_table_prefix"`
PipeProtocolPrefix string `ini:"pipe_protocol_prefix"`
Type string `ini:"type"`
PeerTablePrefix string `ini:"peer_table_prefix"`
PipeProtocolPrefix string `ini:"pipe_protocol_prefix"`
NeighborsRefreshTimeout int `ini:"neighbors_refresh_timeout"`
}

View File

@ -200,6 +200,31 @@ func parseNeighbours(bird ClientResponse, config Config) (api.Neighbours, error)
return neighbours, nil
}
// Parse neighbours response
func parseNeighboursShort(bird ClientResponse, config Config) (api.NeighboursStatus, error) {
neighbours := api.NeighboursStatus{}
protocols := bird["protocols"].(map[string]interface{})
// Iterate over protocols map:
for protocolId, proto := range protocols {
protocol := proto.(map[string]interface{})
uptime := parseRelativeServerTime(protocol["since"], config)
neighbour := &api.NeighbourStatus{
Id: protocolId,
State: mustString(protocol["state"], "unknown"),
Since: uptime,
}
neighbours = append(neighbours, neighbour)
}
sort.Sort(neighbours)
return neighbours, nil
}
// Parse route bgp info
func parseRouteBgpInfo(data interface{}) api.BgpInfo {
bgpData, ok := data.(map[string]interface{})

View File

@ -5,7 +5,9 @@ import (
"github.com/alice-lg/alice-lg/backend/caches"
"github.com/alice-lg/alice-lg/backend/sources"
"fmt"
"sort"
"time"
)
type Birdwatcher interface {
@ -145,6 +147,47 @@ func (self *GenericBirdwatcher) filterRoutesByDuplicates(routes api.Routes, filt
return routes
}
func (self *GenericBirdwatcher) filterRoutesByNeighborId(routes api.Routes, neighborId string) api.Routes {
result_routes := make(api.Routes, 0, len(routes))
// Choose routes with next_hop == gateway of this neighbour
for _, route := range routes {
if route.Details["from_protocol"] == neighborId {
result_routes = append(result_routes, route)
}
}
// Sort routes for deterministic ordering
sort.Sort(result_routes)
routes = result_routes
return routes
}
func (self *GenericBirdwatcher) fetchProtocolsShort() (*api.ApiStatus, map[string]interface{}, error) {
// Query birdwatcher
timeout := 2 * time.Second
if self.config.NeighborsRefreshTimeout > 0 {
timeout = time.Duration(self.config.NeighborsRefreshTimeout) * time.Second
}
bird, err := self.client.GetJsonTimeout(timeout, "/protocols/short?uncached=true")
if err != nil {
return nil, nil, err
}
// Use api status from first request
apiStatus, err := parseApiStatus(bird, self.config)
if err != nil {
return nil, nil, err
}
if _, ok := bird["protocols"]; !ok {
return nil, nil, fmt.Errorf("Failed to fetch protocols")
}
return &apiStatus, bird, nil
}
func (self *GenericBirdwatcher) ExpireCaches() int {
count := self.routesRequiredCache.Expire()
count += self.routesNotExportedCache.Expire()
@ -179,6 +222,28 @@ func (self *GenericBirdwatcher) Status() (*api.StatusResponse, error) {
return response, nil
}
// Get live neighbor status
func (self *GenericBirdwatcher) NeighboursStatus() (*api.NeighboursStatusResponse, error) {
// Query birdwatcher
apiStatus, birdProtocols, err := self.fetchProtocolsShort()
if err != nil {
return nil, err
}
// Parse the neighbors short
neighbours, err := parseNeighboursShort(birdProtocols, self.config)
if err != nil {
return nil, err
}
response := &api.NeighboursStatusResponse{
Api: *apiStatus,
Neighbours: neighbours,
}
return response, nil // dereference for now
}
// Make routes lookup
func (self *GenericBirdwatcher) LookupPrefix(prefix string) (*api.RoutesLookupResponse, error) {
// Get RS info

View File

@ -8,6 +8,7 @@ type Source interface {
ExpireCaches() int
Status() (*api.StatusResponse, error)
Neighbours() (*api.NeighboursResponse, error)
NeighboursStatus() (*api.NeighboursStatusResponse, error)
Routes(neighbourId string) (*api.RoutesResponse, error)
RoutesReceived(neighbourId string) (*api.RoutesResponse, error)
RoutesFiltered(neighbourId string) (*api.RoutesResponse, error)

View File

@ -5,8 +5,10 @@
[server]
# configures the built-in webserver and provides global application settings
listen_http = 127.0.0.1:7340
enable_prefix_lookup = true
# enable the prefix-lookup endpoint / the global search feature
enable_prefix_lookup = true
# Try to refresh the neighbor status on every request to /neighbors
enable_neighbors_status_refresh = false
asn = 9033
# this ASN is used as a fallback value in the RPKI feature and for route
# filtering evaluation with large BGP communities
@ -150,6 +152,8 @@ api = http://rs1.example.com:29184/
type = multi_table
peer_table_prefix = T
pipe_protocol_prefix = M
# Timeout in seconds to wait for the status data (only required if enable_neighbors_status_refresh is true)
neighbors_refresh_timeout = 2
# Optional:
show_last_reboot = true
@ -164,6 +168,8 @@ api = http://rs1.example.com:29186/
type = multi_table
peer_table_prefix = T
pipe_protocol_prefix = M
# Timeout in seconds to wait for the status data (only required if enable_neighbors_status_refresh is true)
neighbors_refresh_timeout = 2
# Optional: Examples for time format
# Please see https://golang.org/pkg/time/#pkg-constants for an