2021-03-22 16:25:47 +01:00
|
|
|
package backend
|
2017-06-23 11:12:24 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"log"
|
2017-06-23 16:11:47 +02:00
|
|
|
"strings"
|
2017-07-14 14:18:47 +02:00
|
|
|
"sync"
|
2017-06-23 11:12:24 +02:00
|
|
|
"time"
|
2017-07-14 14:18:47 +02:00
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
"github.com/alice-lg/alice-lg/pkg/api"
|
2017-06-23 11:12:24 +02:00
|
|
|
)
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// The RoutesStore holds a mapping of routes,
|
|
|
|
// status and configs and will be queried instead
|
|
|
|
// of a backend by the API
|
2017-06-23 11:12:24 +02:00
|
|
|
type RoutesStore struct {
|
2018-12-09 17:31:02 +01:00
|
|
|
routesMap map[string]*api.RoutesResponse
|
|
|
|
statusMap map[string]StoreStatus
|
|
|
|
configMap map[string]*SourceConfig
|
2018-09-25 21:26:11 +02:00
|
|
|
|
2018-07-07 11:51:13 +02:00
|
|
|
refreshInterval time.Duration
|
2018-09-25 21:26:11 +02:00
|
|
|
lastRefresh time.Time
|
2017-07-14 14:18:47 +02:00
|
|
|
|
2018-06-26 10:51:11 +02:00
|
|
|
sync.RWMutex
|
2017-06-23 11:12:24 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// NewRoutesStore makes a new store instance
|
|
|
|
// with a config.
|
2017-06-23 11:12:24 +02:00
|
|
|
func NewRoutesStore(config *Config) *RoutesStore {
|
|
|
|
|
|
|
|
// Build mapping based on source instances
|
2018-12-09 17:31:02 +01:00
|
|
|
routesMap := make(map[string]*api.RoutesResponse)
|
|
|
|
statusMap := make(map[string]StoreStatus)
|
|
|
|
configMap := make(map[string]*SourceConfig)
|
2017-06-23 14:25:40 +02:00
|
|
|
|
2017-06-23 11:12:24 +02:00
|
|
|
for _, source := range config.Sources {
|
2017-06-23 18:01:49 +02:00
|
|
|
id := source.Id
|
|
|
|
|
|
|
|
configMap[id] = source
|
2018-07-11 18:25:42 +02:00
|
|
|
routesMap[id] = &api.RoutesResponse{}
|
2017-06-23 18:01:49 +02:00
|
|
|
statusMap[id] = StoreStatus{
|
2017-06-23 12:36:32 +02:00
|
|
|
State: STATE_INIT,
|
|
|
|
}
|
2017-06-23 11:12:24 +02:00
|
|
|
}
|
|
|
|
|
2018-07-07 11:51:13 +02:00
|
|
|
// Set refresh interval as duration, fall back to
|
|
|
|
// five minutes if no interval is set.
|
|
|
|
refreshInterval := time.Duration(
|
|
|
|
config.Server.RoutesStoreRefreshInterval) * time.Minute
|
|
|
|
if refreshInterval == 0 {
|
|
|
|
refreshInterval = time.Duration(5) * time.Minute
|
|
|
|
}
|
|
|
|
|
2017-06-23 11:12:24 +02:00
|
|
|
store := &RoutesStore{
|
2018-04-11 17:15:14 +02:00
|
|
|
routesMap: routesMap,
|
|
|
|
statusMap: statusMap,
|
|
|
|
configMap: configMap,
|
2018-07-07 11:51:13 +02:00
|
|
|
refreshInterval: refreshInterval,
|
2017-06-23 11:12:24 +02:00
|
|
|
}
|
|
|
|
return store
|
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// Start starts the routes store
|
|
|
|
func (rs *RoutesStore) Start() {
|
2017-06-23 11:12:24 +02:00
|
|
|
log.Println("Starting local routes store")
|
2021-03-22 17:35:20 +01:00
|
|
|
log.Println("Routes Store refresh interval set to:", rs.refreshInterval)
|
|
|
|
if err := rs.init(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2017-06-23 14:25:40 +02:00
|
|
|
}
|
2017-06-23 11:12:24 +02:00
|
|
|
|
2017-06-23 14:25:40 +02:00
|
|
|
// Service initialization
|
2021-03-22 17:35:20 +01:00
|
|
|
func (rs *RoutesStore) init() error {
|
2017-06-23 12:36:32 +02:00
|
|
|
// Initial refresh
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.update()
|
2017-06-23 14:25:40 +02:00
|
|
|
|
|
|
|
// Initial stats
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.Stats().Log()
|
2017-06-23 16:25:21 +02:00
|
|
|
|
|
|
|
// Periodically update store
|
|
|
|
for {
|
2021-03-22 17:35:20 +01:00
|
|
|
time.Sleep(rs.refreshInterval)
|
|
|
|
rs.update()
|
2017-06-23 16:25:21 +02:00
|
|
|
}
|
2017-06-23 11:12:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Update all routes
|
2021-03-22 17:35:20 +01:00
|
|
|
func (rs *RoutesStore) update() {
|
2018-10-01 12:13:40 +02:00
|
|
|
successCount := 0
|
|
|
|
errorCount := 0
|
2018-10-01 12:02:14 +02:00
|
|
|
t0 := time.Now()
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
for sourceID := range rs.routesMap {
|
|
|
|
sourceConfig := rs.configMap[sourceID]
|
2018-10-01 12:02:14 +02:00
|
|
|
source := sourceConfig.getInstance()
|
2017-06-23 18:01:49 +02:00
|
|
|
|
2017-06-23 12:36:32 +02:00
|
|
|
// Get current update state
|
2021-03-22 17:35:20 +01:00
|
|
|
if rs.statusMap[sourceID].State == STATE_UPDATING {
|
2017-06-23 12:36:32 +02:00
|
|
|
continue // nothing to do here
|
|
|
|
}
|
|
|
|
|
2018-07-13 11:38:20 +02:00
|
|
|
// Set update state
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.Lock()
|
|
|
|
rs.statusMap[sourceID] = StoreStatus{
|
2017-06-23 12:36:32 +02:00
|
|
|
State: STATE_UPDATING,
|
|
|
|
}
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.Unlock()
|
2017-06-23 12:36:32 +02:00
|
|
|
|
2017-06-23 11:12:24 +02:00
|
|
|
routes, err := source.AllRoutes()
|
|
|
|
if err != nil {
|
2018-10-01 12:02:14 +02:00
|
|
|
log.Println(
|
|
|
|
"Refreshing the routes store failed for:", sourceConfig.Name,
|
|
|
|
"(", sourceConfig.Id, ")",
|
|
|
|
"with:", err,
|
|
|
|
"- NEXT STATE: ERROR",
|
|
|
|
)
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.Lock()
|
|
|
|
rs.statusMap[sourceID] = StoreStatus{
|
2017-06-23 12:36:32 +02:00
|
|
|
State: STATE_ERROR,
|
|
|
|
LastError: err,
|
|
|
|
LastRefresh: time.Now(),
|
|
|
|
}
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.Unlock()
|
2018-07-13 11:41:00 +02:00
|
|
|
|
2018-10-01 12:13:40 +02:00
|
|
|
errorCount++
|
2017-06-23 11:12:24 +02:00
|
|
|
continue
|
|
|
|
}
|
2017-06-23 12:36:32 +02:00
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.Lock()
|
2017-06-23 12:36:32 +02:00
|
|
|
// Update data
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.routesMap[sourceID] = routes
|
2017-06-23 12:36:32 +02:00
|
|
|
// Update state
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.statusMap[sourceID] = StoreStatus{
|
2017-06-23 12:36:32 +02:00
|
|
|
LastRefresh: time.Now(),
|
|
|
|
State: STATE_READY,
|
|
|
|
}
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.lastRefresh = time.Now().UTC()
|
|
|
|
rs.Unlock()
|
2018-10-01 12:13:40 +02:00
|
|
|
|
|
|
|
successCount++
|
2017-06-23 11:12:24 +02:00
|
|
|
}
|
2018-10-01 12:02:14 +02:00
|
|
|
|
|
|
|
refreshDuration := time.Since(t0)
|
|
|
|
log.Println(
|
2018-10-01 12:13:40 +02:00
|
|
|
"Refreshed routes store for", successCount, "of", successCount+errorCount,
|
|
|
|
"sources with", errorCount, "error(s) in", refreshDuration,
|
2018-10-01 12:02:14 +02:00
|
|
|
)
|
|
|
|
|
2017-06-23 11:12:24 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// Stats calculates some store insights
|
|
|
|
func (rs *RoutesStore) Stats() RoutesStoreStats {
|
2017-06-23 12:55:08 +02:00
|
|
|
totalImported := 0
|
|
|
|
totalFiltered := 0
|
|
|
|
|
2017-06-23 17:40:19 +02:00
|
|
|
rsStats := []RouteServerRoutesStats{}
|
2017-06-23 12:55:08 +02:00
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.RLock()
|
|
|
|
for sourceID, routes := range rs.routesMap {
|
|
|
|
status := rs.statusMap[sourceID]
|
2017-06-23 12:55:08 +02:00
|
|
|
|
|
|
|
totalImported += len(routes.Imported)
|
|
|
|
totalFiltered += len(routes.Filtered)
|
|
|
|
|
2017-06-23 17:40:19 +02:00
|
|
|
serverStats := RouteServerRoutesStats{
|
2021-03-22 17:35:20 +01:00
|
|
|
Name: rs.configMap[sourceID].Name,
|
2017-06-23 14:25:40 +02:00
|
|
|
|
2017-06-23 12:55:08 +02:00
|
|
|
Routes: RoutesStats{
|
|
|
|
Filtered: len(routes.Filtered),
|
|
|
|
Imported: len(routes.Imported),
|
|
|
|
},
|
|
|
|
|
|
|
|
State: stateToString(status.State),
|
|
|
|
UpdatedAt: status.LastRefresh,
|
|
|
|
}
|
|
|
|
|
|
|
|
rsStats = append(rsStats, serverStats)
|
|
|
|
}
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.RUnlock()
|
2017-06-23 12:55:08 +02:00
|
|
|
|
|
|
|
// Make stats
|
2017-06-23 16:25:21 +02:00
|
|
|
storeStats := RoutesStoreStats{
|
2017-06-23 12:55:08 +02:00
|
|
|
TotalRoutes: RoutesStats{
|
|
|
|
Imported: totalImported,
|
|
|
|
Filtered: totalFiltered,
|
|
|
|
},
|
|
|
|
RouteServers: rsStats,
|
|
|
|
}
|
|
|
|
return storeStats
|
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// CachedAt provides a cache status
|
|
|
|
func (rs *RoutesStore) CachedAt() time.Time {
|
|
|
|
return rs.lastRefresh
|
2018-09-25 21:26:11 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// CacheTTL returns the TTL time
|
|
|
|
func (rs *RoutesStore) CacheTTL() time.Time {
|
|
|
|
return rs.lastRefresh.Add(rs.refreshInterval)
|
2018-09-25 21:26:11 +02:00
|
|
|
}
|
|
|
|
|
2017-06-30 14:15:43 +02:00
|
|
|
// Lookup routes transform
|
2018-07-11 14:07:26 +02:00
|
|
|
func routeToLookupRoute(
|
|
|
|
source *SourceConfig,
|
|
|
|
state string,
|
|
|
|
route *api.Route,
|
|
|
|
) *api.LookupRoute {
|
2017-06-30 14:15:43 +02:00
|
|
|
|
|
|
|
// Get neighbour
|
|
|
|
neighbour := AliceNeighboursStore.GetNeighbourAt(source.Id, route.NeighbourId)
|
|
|
|
|
|
|
|
// Make route
|
2018-07-06 17:04:09 +02:00
|
|
|
lookup := &api.LookupRoute{
|
2017-06-30 14:15:43 +02:00
|
|
|
Id: route.Id,
|
|
|
|
|
|
|
|
NeighbourId: route.NeighbourId,
|
|
|
|
Neighbour: neighbour,
|
|
|
|
|
|
|
|
Routeserver: api.Routeserver{
|
|
|
|
Id: source.Id,
|
|
|
|
Name: source.Name,
|
|
|
|
},
|
|
|
|
|
|
|
|
State: state,
|
|
|
|
|
|
|
|
Network: route.Network,
|
|
|
|
Interface: route.Interface,
|
|
|
|
Gateway: route.Gateway,
|
|
|
|
Metric: route.Metric,
|
|
|
|
Bgp: route.Bgp,
|
|
|
|
Age: route.Age,
|
|
|
|
Type: route.Type,
|
2018-08-02 14:30:40 +02:00
|
|
|
Primary: route.Primary,
|
2017-06-30 14:15:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return lookup
|
|
|
|
}
|
|
|
|
|
2017-06-23 16:11:47 +02:00
|
|
|
// Routes filter
|
2017-06-30 14:15:43 +02:00
|
|
|
func filterRoutesByPrefix(
|
2018-07-11 14:07:26 +02:00
|
|
|
source *SourceConfig,
|
2018-07-07 11:35:34 +02:00
|
|
|
routes api.Routes,
|
2017-06-23 16:11:47 +02:00
|
|
|
prefix string,
|
|
|
|
state string,
|
2018-07-07 11:35:34 +02:00
|
|
|
) api.LookupRoutes {
|
|
|
|
results := api.LookupRoutes{}
|
2017-06-23 16:11:47 +02:00
|
|
|
for _, route := range routes {
|
|
|
|
// Naiive filtering:
|
2018-11-09 12:46:06 +01:00
|
|
|
if strings.HasPrefix(strings.ToLower(route.Network), prefix) {
|
2017-06-30 14:15:43 +02:00
|
|
|
lookup := routeToLookupRoute(source, state, route)
|
2017-06-23 16:11:47 +02:00
|
|
|
results = append(results, lookup)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results
|
|
|
|
}
|
|
|
|
|
2017-06-30 14:15:43 +02:00
|
|
|
func filterRoutesByNeighbourIds(
|
2018-07-11 14:07:26 +02:00
|
|
|
source *SourceConfig,
|
2018-07-07 11:35:34 +02:00
|
|
|
routes api.Routes,
|
2017-06-30 14:15:43 +02:00
|
|
|
neighbourIds []string,
|
|
|
|
state string,
|
2018-07-07 11:35:34 +02:00
|
|
|
) api.LookupRoutes {
|
2017-06-30 14:15:43 +02:00
|
|
|
|
2018-07-07 11:35:34 +02:00
|
|
|
results := api.LookupRoutes{}
|
2017-06-30 14:15:43 +02:00
|
|
|
for _, route := range routes {
|
|
|
|
// Filtering:
|
|
|
|
if MemberOf(neighbourIds, route.NeighbourId) == true {
|
|
|
|
lookup := routeToLookupRoute(source, state, route)
|
|
|
|
results = append(results, lookup)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return results
|
2017-06-23 17:40:19 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// LookupNeighboursPrefixesAt performs a single route server
|
|
|
|
// routes lookup by neighbor id
|
|
|
|
func (rs *RoutesStore) LookupNeighboursPrefixesAt(
|
|
|
|
sourceID string,
|
2017-06-30 14:15:43 +02:00
|
|
|
neighbourIds []string,
|
2018-07-07 11:35:34 +02:00
|
|
|
) chan api.LookupRoutes {
|
|
|
|
response := make(chan api.LookupRoutes)
|
2017-06-30 11:12:15 +02:00
|
|
|
|
2017-06-30 14:15:43 +02:00
|
|
|
go func() {
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.RLock()
|
|
|
|
source := rs.configMap[sourceID]
|
|
|
|
routes := rs.routesMap[sourceID]
|
|
|
|
rs.RUnlock()
|
2017-06-30 14:15:43 +02:00
|
|
|
|
|
|
|
filtered := filterRoutesByNeighbourIds(
|
|
|
|
source,
|
|
|
|
routes.Filtered,
|
|
|
|
neighbourIds,
|
|
|
|
"filtered")
|
|
|
|
imported := filterRoutesByNeighbourIds(
|
|
|
|
source,
|
|
|
|
routes.Imported,
|
|
|
|
neighbourIds,
|
|
|
|
"imported")
|
|
|
|
|
2018-07-07 11:45:34 +02:00
|
|
|
var result api.LookupRoutes
|
2017-06-30 14:15:43 +02:00
|
|
|
result = append(filtered, imported...)
|
|
|
|
|
|
|
|
response <- result
|
|
|
|
}()
|
|
|
|
|
|
|
|
return response
|
2017-06-30 11:12:15 +02:00
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// LookupPrefixAt performs a single RS lookup
|
|
|
|
func (rs *RoutesStore) LookupPrefixAt(
|
|
|
|
sourceID string,
|
2017-06-23 16:11:47 +02:00
|
|
|
prefix string,
|
2018-07-07 11:35:34 +02:00
|
|
|
) chan api.LookupRoutes {
|
2017-06-23 16:11:47 +02:00
|
|
|
|
2018-07-07 11:35:34 +02:00
|
|
|
response := make(chan api.LookupRoutes)
|
2017-06-23 16:11:47 +02:00
|
|
|
|
|
|
|
go func() {
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.RLock()
|
|
|
|
config := rs.configMap[sourceID]
|
|
|
|
routes := rs.routesMap[sourceID]
|
|
|
|
rs.RUnlock()
|
2017-06-23 17:40:19 +02:00
|
|
|
|
2017-06-30 14:15:43 +02:00
|
|
|
filtered := filterRoutesByPrefix(
|
2017-06-23 16:11:47 +02:00
|
|
|
config,
|
|
|
|
routes.Filtered,
|
|
|
|
prefix,
|
|
|
|
"filtered")
|
2017-06-30 14:15:43 +02:00
|
|
|
imported := filterRoutesByPrefix(
|
2017-06-23 16:11:47 +02:00
|
|
|
config,
|
|
|
|
routes.Imported,
|
|
|
|
prefix,
|
|
|
|
"imported")
|
|
|
|
|
2018-07-07 11:45:34 +02:00
|
|
|
var result api.LookupRoutes
|
2017-06-30 14:15:43 +02:00
|
|
|
result = append(filtered, imported...)
|
2017-06-23 16:11:47 +02:00
|
|
|
|
|
|
|
response <- result
|
|
|
|
}()
|
|
|
|
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// LookupPrefix performs a lookup over all route servers
|
|
|
|
func (rs *RoutesStore) LookupPrefix(prefix string) api.LookupRoutes {
|
2018-07-07 11:35:34 +02:00
|
|
|
result := api.LookupRoutes{}
|
|
|
|
responses := []chan api.LookupRoutes{}
|
2017-06-23 16:11:47 +02:00
|
|
|
|
2018-11-09 12:46:06 +01:00
|
|
|
// Normalize prefix to lower case
|
|
|
|
prefix = strings.ToLower(prefix)
|
|
|
|
|
2017-06-23 16:11:47 +02:00
|
|
|
// Dispatch
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.RLock()
|
|
|
|
for sourceID := range rs.routesMap {
|
|
|
|
res := rs.LookupPrefixAt(sourceID, prefix)
|
2017-06-23 16:11:47 +02:00
|
|
|
responses = append(responses, res)
|
|
|
|
}
|
2021-03-22 17:35:20 +01:00
|
|
|
rs.RUnlock()
|
2017-06-23 16:11:47 +02:00
|
|
|
|
|
|
|
// Collect
|
|
|
|
for _, response := range responses {
|
|
|
|
routes := <-response
|
|
|
|
result = append(result, routes...)
|
2017-06-30 14:15:43 +02:00
|
|
|
close(response)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
// LookupPrefixForNeighbours returns all routes for
|
|
|
|
// a set of neighbors.
|
|
|
|
func (rs *RoutesStore) LookupPrefixForNeighbours(
|
2017-06-30 14:15:43 +02:00
|
|
|
neighbours api.NeighboursLookupResults,
|
2018-07-07 11:35:34 +02:00
|
|
|
) api.LookupRoutes {
|
2017-06-30 14:15:43 +02:00
|
|
|
|
2018-07-07 11:35:34 +02:00
|
|
|
result := api.LookupRoutes{}
|
|
|
|
responses := []chan api.LookupRoutes{}
|
2017-06-30 14:15:43 +02:00
|
|
|
|
|
|
|
// Dispatch
|
2021-03-22 17:35:20 +01:00
|
|
|
for sourceID, locals := range neighbours {
|
2017-06-30 14:15:43 +02:00
|
|
|
lookupNeighbourIds := []string{}
|
|
|
|
for _, n := range locals {
|
|
|
|
lookupNeighbourIds = append(lookupNeighbourIds, n.Id)
|
|
|
|
}
|
|
|
|
|
2021-03-22 17:35:20 +01:00
|
|
|
res := rs.LookupNeighboursPrefixesAt(sourceID, lookupNeighbourIds)
|
2017-06-30 14:15:43 +02:00
|
|
|
responses = append(responses, res)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collect
|
|
|
|
for _, response := range responses {
|
|
|
|
routes := <-response
|
|
|
|
result = append(result, routes...)
|
|
|
|
close(response)
|
2017-06-23 16:11:47 +02:00
|
|
|
}
|
2017-06-23 11:12:24 +02:00
|
|
|
|
|
|
|
return result
|
|
|
|
}
|