mirror of
https://github.com/alice-lg/alice-lg.git
synced 2024-05-11 05:55:03 +00:00
308 lines
7.3 KiB
Go
308 lines
7.3 KiB
Go
package birdwatcher
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/alice-lg/alice-lg/pkg/api"
|
|
"github.com/alice-lg/alice-lg/pkg/caches"
|
|
"github.com/alice-lg/alice-lg/pkg/sources"
|
|
)
|
|
|
|
// A Birdwatcher source is a variant of an alice
|
|
// source and implements different strategies for fetching
|
|
// route information from bird.
|
|
type Birdwatcher interface {
|
|
sources.Source
|
|
}
|
|
|
|
// GenericBirdwatcher is an Alice data source.
|
|
type GenericBirdwatcher struct {
|
|
config Config
|
|
client *Client
|
|
|
|
// Caches: Neighbors
|
|
neighborsCache *caches.NeighborsCache
|
|
|
|
// Caches: Routes
|
|
routesRequiredCache *caches.RoutesCache
|
|
routesNotExportedCache *caches.RoutesCache
|
|
|
|
// Mutices:
|
|
routesFetchMutex *LockMap
|
|
}
|
|
|
|
// NewBirdwatcher creates a new Birdwatcher instance.
|
|
// This might be either a GenericBirdWatcher or a MultiTableBirdwatcher.
|
|
func NewBirdwatcher(config Config) Birdwatcher {
|
|
client := NewClient(config.API)
|
|
|
|
// Cache settings:
|
|
// TODO: Maybe read from config file
|
|
neighborsCacheDisable := false
|
|
|
|
routesCacheDisabled := false
|
|
routesCacheMaxSize := 128
|
|
|
|
// Initialize caches
|
|
neighborsCache := caches.NewNeighborsCache(neighborsCacheDisable)
|
|
routesRequiredCache := caches.NewRoutesCache(
|
|
routesCacheDisabled, routesCacheMaxSize)
|
|
routesNotExportedCache := caches.NewRoutesCache(
|
|
routesCacheDisabled, routesCacheMaxSize)
|
|
|
|
var birdwatcher Birdwatcher
|
|
|
|
if config.Type == "single_table" {
|
|
singleTableBirdwatcher := new(SingleTableBirdwatcher)
|
|
|
|
singleTableBirdwatcher.config = config
|
|
singleTableBirdwatcher.client = client
|
|
|
|
singleTableBirdwatcher.neighborsCache = neighborsCache
|
|
|
|
singleTableBirdwatcher.routesRequiredCache = routesRequiredCache
|
|
singleTableBirdwatcher.routesNotExportedCache = routesNotExportedCache
|
|
|
|
singleTableBirdwatcher.routesFetchMutex = NewLockMap()
|
|
|
|
birdwatcher = singleTableBirdwatcher
|
|
} else if config.Type == "multi_table" {
|
|
multiTableBirdwatcher := new(MultiTableBirdwatcher)
|
|
|
|
multiTableBirdwatcher.config = config
|
|
multiTableBirdwatcher.client = client
|
|
|
|
multiTableBirdwatcher.neighborsCache = neighborsCache
|
|
|
|
multiTableBirdwatcher.routesRequiredCache = routesRequiredCache
|
|
multiTableBirdwatcher.routesNotExportedCache = routesNotExportedCache
|
|
|
|
multiTableBirdwatcher.routesFetchMutex = NewLockMap()
|
|
|
|
birdwatcher = multiTableBirdwatcher
|
|
}
|
|
|
|
return birdwatcher
|
|
}
|
|
|
|
func (b *GenericBirdwatcher) filterProtocols(
|
|
protocols map[string]interface{},
|
|
protocol string,
|
|
) map[string]interface{} {
|
|
response := make(map[string]interface{})
|
|
response["protocols"] = make(map[string]interface{})
|
|
|
|
for protocolID, protocolData := range protocols {
|
|
if protocolData.(map[string]interface{})["bird_protocol"] == protocol {
|
|
response["protocols"].(map[string]interface{})[protocolID] = protocolData
|
|
}
|
|
}
|
|
|
|
return response
|
|
}
|
|
|
|
func (b *GenericBirdwatcher) filterProtocolsBgp(
|
|
bird ClientResponse,
|
|
) map[string]interface{} {
|
|
return b.filterProtocols(bird["protocols"].(map[string]interface{}), "BGP")
|
|
}
|
|
|
|
func (b *GenericBirdwatcher) filterProtocolsPipe(
|
|
bird ClientResponse,
|
|
) map[string]interface{} {
|
|
return b.filterProtocols(bird["protocols"].(map[string]interface{}), "Pipe")
|
|
}
|
|
|
|
func (b *GenericBirdwatcher) filterRoutesByPeerOrLearntFrom(
|
|
routes api.Routes,
|
|
peerPtr *string,
|
|
learntFromPtr *string,
|
|
) api.Routes {
|
|
resultRoutes := make(api.Routes, 0, len(routes))
|
|
|
|
// Choose routes with next_hop == gateway of this neighbor
|
|
for _, route := range routes {
|
|
if (route.Gateway == peerPtr) ||
|
|
(route.Gateway == learntFromPtr) ||
|
|
(route.LearntFrom == peerPtr) {
|
|
resultRoutes = append(resultRoutes, route)
|
|
}
|
|
}
|
|
|
|
// Sort routes for deterministic ordering
|
|
sort.Sort(resultRoutes)
|
|
routes = resultRoutes
|
|
|
|
return routes
|
|
}
|
|
|
|
func (b *GenericBirdwatcher) filterRoutesByDuplicates(
|
|
routes api.Routes,
|
|
filterRoutes api.Routes,
|
|
) api.Routes {
|
|
resultRoutes := make(api.Routes, 0, len(routes))
|
|
|
|
routesMap := make(map[string]*api.Route) // for O(1) access
|
|
for _, route := range routes {
|
|
routesMap[route.ID] = route
|
|
}
|
|
|
|
// Remove routes from "routes" that are contained within filterRoutes
|
|
for _, filterRoute := range filterRoutes {
|
|
delete(routesMap, filterRoute.ID)
|
|
}
|
|
|
|
for _, route := range routesMap {
|
|
resultRoutes = append(resultRoutes, route)
|
|
}
|
|
|
|
// Sort routes for deterministic ordering
|
|
sort.Sort(resultRoutes)
|
|
routes = resultRoutes // TODO: Check if this even makes sense...
|
|
|
|
return routes
|
|
}
|
|
|
|
func (b *GenericBirdwatcher) fetchProtocolsShort(ctx context.Context) (
|
|
*api.Meta,
|
|
map[string]interface{},
|
|
error,
|
|
) {
|
|
// Query birdwatcher with forced timeout
|
|
timeout := 20 * time.Second
|
|
if b.config.NeighborsRefreshTimeout > 0 {
|
|
timeout = time.Duration(b.config.NeighborsRefreshTimeout) * time.Second
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
bird, err := b.client.GetJSON(ctx, "/protocols/short?uncached=true")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Use api status from first request
|
|
apiStatus, err := parseAPIStatus(bird, b.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
|
|
}
|
|
|
|
// ExpireCaches clears all local caches
|
|
func (b *GenericBirdwatcher) ExpireCaches() int {
|
|
count := b.routesRequiredCache.Expire()
|
|
count += b.routesNotExportedCache.Expire()
|
|
return count
|
|
}
|
|
|
|
// Status retrievs the current backend status
|
|
func (b *GenericBirdwatcher) Status(ctx context.Context) (*api.StatusResponse, error) {
|
|
bird, err := b.client.GetJSON(ctx, "/status")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Use api status from first request
|
|
apiStatus, err := parseAPIStatus(bird, b.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse the status
|
|
birdStatus, err := parseBirdwatcherStatus(bird, b.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &api.StatusResponse{
|
|
Response: api.Response{
|
|
Meta: apiStatus,
|
|
},
|
|
Status: birdStatus,
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
// NeighborsStatus retrieves neighbor status infos
|
|
func (b *GenericBirdwatcher) NeighborsStatus(ctx context.Context) (
|
|
*api.NeighborsStatusResponse,
|
|
error,
|
|
) {
|
|
// Query birdwatcher
|
|
apiStatus, birdProtocols, err := b.fetchProtocolsShort(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse the neighbors short
|
|
neighbors, err := parseNeighborsShort(birdProtocols, b.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
response := &api.NeighborsStatusResponse{
|
|
Response: api.Response{
|
|
Meta: apiStatus,
|
|
},
|
|
Neighbors: neighbors,
|
|
}
|
|
return response, nil // dereference for now
|
|
}
|
|
|
|
// LookupPrefix makes a routes lookup
|
|
func (b *GenericBirdwatcher) LookupPrefix(
|
|
ctx context.Context,
|
|
prefix string,
|
|
) (*api.RoutesLookupResponse, error) {
|
|
// Get RS info
|
|
rs := &api.RouteServer{
|
|
ID: b.config.ID,
|
|
Name: b.config.Name,
|
|
}
|
|
|
|
// Query prefix on RS
|
|
bird, err := b.client.GetJSON(ctx, "/routes/prefix?prefix="+prefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse API status
|
|
apiStatus, err := parseAPIStatus(bird, b.config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse routes
|
|
routes, _ := parseRoutes(bird, b.config, true)
|
|
|
|
// Add corresponding neighbor and source rs to result
|
|
results := api.LookupRoutes{}
|
|
for _, src := range routes {
|
|
route := &api.LookupRoute{
|
|
RouteServer: rs,
|
|
Route: src,
|
|
}
|
|
results = append(results, route)
|
|
}
|
|
|
|
// Make result
|
|
response := &api.RoutesLookupResponse{
|
|
Response: api.Response{
|
|
Meta: apiStatus,
|
|
},
|
|
Routes: results,
|
|
}
|
|
return response, nil
|
|
}
|