diff --git a/Makefile b/Makefile index 0c720ea..8c42c09 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,13 @@ backend_prod: client_prod backend: $(MAKE) -C cmd/alice-lg/ linux -alice: backend_prod +backend_tests: + go test ./pkg/... + +test: backend_tests + + +alice: backend_tests backend_prod cp cmd/alice-lg/alice-lg-* bin/ diff --git a/cmd/alice-lg/Makefile b/cmd/alice-lg/Makefile index 616abbe..b6f7cdc 100644 --- a/cmd/alice-lg/Makefile +++ b/cmd/alice-lg/Makefile @@ -19,8 +19,8 @@ else endif -LDFLAGS=-ldflags="-X github.com/alice-lg/alice-lg/pkg/backend.Version=$(APP_VERSION)" -LDFLAGS_STATIC=-ldflags="-X github.com/alice-lg/alice-lg/pkg/backend.Version=$(APP_VERSION) -extldflags '-static'" +LDFLAGS=-ldflags="-X github.com/alice-lg/alice-lg/pkg/config.Version=$(APP_VERSION)" +LDFLAGS_STATIC=-ldflags="-X github.com/alice-lg/alice-lg/pkg/config.Version=$(APP_VERSION) -extldflags '-static'" FILES=$(shell find . -depth 1 ! -name "*_test.go" -name "*.go") diff --git a/cmd/alice-lg/main.go b/cmd/alice-lg/main.go index 231bda9..af60989 100644 --- a/cmd/alice-lg/main.go +++ b/cmd/alice-lg/main.go @@ -1,14 +1,18 @@ package main import ( + "context" "flag" "log" + "github.com/alice-lg/alice-lg/pkg/backend" + "github.com/alice-lg/alice-lg/pkg/config" "github.com/alice-lg/alice-lg/pkg/store" ) func main() { quit := make(chan bool) + ctx := context.Background() // Handle commandline parameters configFilenameFlag := flag.String( @@ -20,7 +24,7 @@ func main() { // Load configuration cfg, err = config.LoadConfig(filename) - if err != nil { + if err != nil { log.Fatal(err) } @@ -33,16 +37,16 @@ func main() { routesStore := store.NewRoutesStore(cfg) // Start stores - if backend.AliceConfig.Server.EnablePrefixLookup == true { - go neighborsStore.Start() - go routesStore.Start() + if config.EnablePrefixLookup == true { + go neighborsStore.Start(ctx) + go routesStore.Start(ctx) } // Start the Housekeeping - go store.Housekeeping(cfg) + go store.StartHousekeeping(cfg) - // Start HTTP API - go backend.StartHTTPServer() + // Start HTTP API + go backend.StartHTTPServer(ctx) <-quit } diff --git a/pkg/backend/housekeeping.go b/pkg/backend/housekeeping.go deleted file mode 100644 index 9b6615c..0000000 --- a/pkg/backend/housekeeping.go +++ /dev/null @@ -1,34 +0,0 @@ -package backend - -import ( - "log" - "runtime/debug" - "time" -) - -// Housekeeping is a background task flushing -// memory and expireing caches. -func Housekeeping(config *Config) { - for { - if config.Housekeeping.Interval > 0 { - time.Sleep(time.Duration(config.Housekeeping.Interval) * time.Minute) - } else { - time.Sleep(5 * time.Minute) - } - - log.Println("Housekeeping started") - - // Expire the caches - log.Println("Expiring caches") - for _, source := range config.Sources { - count := source.getInstance().ExpireCaches() - log.Println("Expired", count, "entries for source", source.Name) - } - - if config.Housekeeping.ForceReleaseMemory { - // Trigger a GC and SCVG run - log.Println("Freeing memory") - debug.FreeOSMemory() - } - } -} diff --git a/pkg/backend/status.go b/pkg/config/status.go similarity index 81% rename from pkg/backend/status.go rename to pkg/config/status.go index e2aefbe..874223c 100644 --- a/pkg/backend/status.go +++ b/pkg/config/status.go @@ -1,4 +1,4 @@ -package backend +package config // Version Alice (set during the build) var Version = "unknown" @@ -8,8 +8,8 @@ var Build = "unknown" // AppStatus contains application status information type AppStatus struct { - Version string `json:"version"` - Routes RoutesStoreStats `json:"routes"` + Version string `json:"version"` + Routes RoutesStoreStats `json:"routes"` Neighbors NeighborsStoreStats `json:"neighbors"` } @@ -29,8 +29,8 @@ func NewAppStatus() (*AppStatus, error) { } status := &AppStatus{ - Version: Version, - Routes: routesStatus, + Version: Version, + Routes: routesStatus, Neighbors: neighborsStatus, } return status, nil diff --git a/pkg/store/housekeeping.go b/pkg/store/housekeeping.go new file mode 100644 index 0000000..93803ce --- /dev/null +++ b/pkg/store/housekeeping.go @@ -0,0 +1,47 @@ +package store + +import ( + "context" + "fmt" + "log" + "runtime/debug" + "time" + + "github.com/alice-lg/alice-lg/pkg/config" +) + +// StartHousekeeping is a background task flushing +// memory and expireing caches. +func StartHousekeeping(ctx context.Context, cfg *config.Config) { + + for { + if cfg.Housekeeping.Interval > 0 { + time.Sleep(time.Duration(cfg.Housekeeping.Interval) * time.Minute) + } else { + time.Sleep(5 * time.Minute) + } + + log.Println("Housekeeping started") + + // Expire the caches + log.Println("Expiring caches") + for _, source := range cfg.Sources { + count := source.getInstance().ExpireCaches() + log.Println("Expired", count, "entries for source", source.Name) + } + + if cfg.Housekeeping.ForceReleaseMemory { + // Trigger a GC and SCVG run + log.Println("Freeing memory") + debug.FreeOSMemory() + } + + // Check if our services are still required + select { + case <-ctx.Done(): + fmt.Println("shutting down Housekeeping...") + return + default: + } + } +} diff --git a/pkg/store/neighbors_store.go b/pkg/store/neighbors_store.go index 0ccf4ea..252df82 100644 --- a/pkg/store/neighbors_store.go +++ b/pkg/store/neighbors_store.go @@ -11,13 +11,16 @@ import ( "github.com/alice-lg/alice-lg/pkg/config" ) -var REGEX_MATCH_ASLOOKUP = regexp.MustCompile(`(?i)^AS(\d+)`) +// ReMatchASLookup matches lookups with an 'AS' prefix +var ReMatchASLookup = regexp.MustCompile(`(?i)^AS(\d+)`) +// NeighborsIndex is a mapping from a string to a neighbor. type NeighborsIndex map[string]*api.Neighbor +// NeighborsStore is queryable for neighbor information type NeighborsStore struct { - neighborsMap map[string]NeighborsIndex - cfgMap map[string]*config.SourceConfig + neighborsMap map[string]NeighborsIndex + cfgMap map[string]*config.SourceConfig statusMap map[string]StoreStatus refreshInterval time.Duration refreshNeighborStatus bool @@ -28,7 +31,6 @@ type NeighborsStore struct { // NewNeighborsStore creates a new store for neighbors func NewNeighborsStore(cfg *config.Config) *NeighborsStore { - // Build source mapping neighborsMap := make(map[string]NeighborsIndex) cfgMap := make(map[string]*config.SourceConfig) @@ -55,9 +57,9 @@ func NewNeighborsStore(cfg *config.Config) *NeighborsStore { refreshNeighborStatus := cfg.Server.EnableNeighborsStatusRefresh store := &NeighborsStore{ - neighborsMap: neighborsMap, + neighborsMap: neighborsMap, statusMap: statusMap, - cfgMap: cfgMap, + cfgMap: cfgMap, refreshInterval: refreshInterval, refreshNeighborStatus: refreshNeighborStatus, } @@ -65,59 +67,59 @@ func NewNeighborsStore(cfg *config.Config) *NeighborsStore { } // Start the store's housekeeping. -func (self *NeighborsStore) Start() { +func (s *NeighborsStore) Start() { log.Println("Starting local neighbors store") - log.Println("Neighbors Store refresh interval set to:", self.refreshInterval) - go self.init() + log.Println("Neighbors Store refresh interval set to:", s.refreshInterval) + go s.init() } -func (self *NeighborsStore) init() { +func (s *NeighborsStore) init() { // Perform initial update - self.update() + s.update() // Initial logging - self.Stats().Log() + s.Stats().Log() // Periodically update store for { - time.Sleep(self.refreshInterval) - self.update() + time.Sleep(s.refreshInterval) + s.update() } } -func (self *NeighborsStore) SourceStatus(sourceID string) StoreStatus { - self.RLock() - status := self.statusMap[sourceID] - self.RUnlock() - - return status +// SourceStatus retrievs the status for a route server +// identified by sourceID. +func (s *NeighborsStore) SourceStatus(sourceID string) StoreStatus { + s.RLock() + defer s.RUnlock() + return s.statusMap[sourceID] } -// Get state by source ID -func (self *NeighborsStore) SourceState(sourceID string) int { - status := self.SourceStatus(sourceID) +// SourceState gets the state by source ID +func (s *NeighborsStore) SourceState(sourceID string) int { + status := s.SourceStatus(sourceID) return status.State } // Update all neighbors -func (self *NeighborsStore) update() { +func (s *NeighborsStore) update() { successCount := 0 errorCount := 0 t0 := time.Now() - for sourceID, _ := range self.neighborsMap { + for sourceID := range s.neighborsMap { // Get current state - if self.statusMap[sourceID].State == STATE_UPDATING { + if s.statusMap[sourceID].State == STATE_UPDATING { continue // nothing to do here. really. } // Start updating - self.Lock() - self.statusMap[sourceID] = StoreStatus{ + s.Lock() + s.statusMap[sourceID] = StoreStatus{ State: STATE_UPDATING, } - self.Unlock() + s.Unlock() - sourceConfig := self.cfgMap[sourceID] + sourceConfig := s.cfgMap[sourceID] source := sourceConfig.GetInstance() neighborsRes, err := source.Neighbors() @@ -129,13 +131,13 @@ func (self *NeighborsStore) update() { "- NEXT STATE: ERROR", ) // That's sad. - self.Lock() - self.statusMap[sourceID] = StoreStatus{ + s.Lock() + s.statusMap[sourceID] = StoreStatus{ State: STATE_ERROR, LastError: err, LastRefresh: time.Now(), } - self.Unlock() + s.Unlock() errorCount++ continue @@ -150,15 +152,15 @@ func (self *NeighborsStore) update() { index[neighbor.ID] = neighbor } - self.Lock() - self.neighborsMap[sourceID] = index + s.Lock() + s.neighborsMap[sourceID] = index // Update state - self.statusMap[sourceID] = StoreStatus{ + s.statusMap[sourceID] = StoreStatus{ LastRefresh: time.Now(), State: STATE_READY, } - self.lastRefresh = time.Now().UTC() - self.Unlock() + s.lastRefresh = time.Now().UTC() + s.Unlock() successCount++ } @@ -169,19 +171,22 @@ func (self *NeighborsStore) update() { ) } -func (self *NeighborsStore) GetNeighborsAt(sourceID string) api.Neighbors { - self.RLock() - neighborsIDx := self.neighborsMap[sourceID] - self.RUnlock() +// GetNeighborsAt gets all neighbors from a routeserver +func (s *NeighborsStore) GetNeighborsAt(sourceID string) api.Neighbors { + s.RLock() + neighborsIDx := s.neighborsMap[sourceID] + s.RUnlock() var neighborsStatus map[string]api.NeighborStatus - if self.refreshNeighborStatus { - sourceConfig := self.cfgMap[sourceID] + if s.refreshNeighborStatus { + sourceConfig := s.cfgMap[sourceID] source := sourceConfig.GetInstance() neighborsStatusData, err := source.NeighborsStatus() if err == nil { - neighborsStatus = make(map[string]api.NeighborStatus, len(neighborsStatusData.Neighbors)) + neighborsStatus = make( + map[string]api.NeighborStatus, + len(neighborsStatusData.Neighbors)) for _, neighbor := range neighborsStatusData.Neighbors { neighborsStatus[neighbor.ID] = *neighbor @@ -190,43 +195,41 @@ func (self *NeighborsStore) GetNeighborsAt(sourceID string) api.Neighbors { } neighbors := make(api.Neighbors, 0, len(neighborsIDx)) - for _, neighbor := range neighborsIDx { - if self.refreshNeighborStatus { + if s.refreshNeighborStatus { if _, ok := neighborsStatus[neighbor.ID]; ok { - self.Lock() + s.Lock() neighbor.State = neighborsStatus[neighbor.ID].State - self.Unlock() + s.Unlock() } } - neighbors = append(neighbors, neighbor) } - return neighbors } -func (self *NeighborsStore) GetNeighborAt( +// GetNeighborAt looks up a neighbor on a RS by ID. +func (s *NeighborsStore) GetNeighborAt( sourceID string, id string, ) *api.Neighbor { - // Lookup neighbor on RS - self.RLock() - neighborsIDx := self.neighborsMap[sourceID] - self.RUnlock() - + s.RLock() + defer s.RUnlock() + neighborsIDx := s.neighborsMap[sourceID] return neighborsIDx[id] } -func (self *NeighborsStore) LookupNeighborsAt( +// LookupNeighborsAt filters for neighbors at a route +// server matching a given query string. +func (s *NeighborsStore) LookupNeighborsAt( sourceID string, query string, ) api.Neighbors { results := api.Neighbors{} - self.RLock() - neighbors := self.neighborsMap[sourceID] - self.RUnlock() + s.RLock() + neighbors := s.neighborsMap[sourceID] + s.RUnlock() asn := -1 if REGEX_MATCH_ASLOOKUP.MatchString(query) { @@ -249,31 +252,28 @@ func (self *NeighborsStore) LookupNeighborsAt( return results } -func (self *NeighborsStore) LookupNeighbors( +// LookupNeighbors filters for neighbors matching a query +// on all route servers. +func (s *NeighborsStore) LookupNeighbors( query string, ) api.NeighborsLookupResults { // Create empty result set results := make(api.NeighborsLookupResults) - - for sourceID, _ := range self.neighborsMap { - results[sourceID] = self.LookupNeighborsAt(sourceID, query) + for sourceID := range s.neighborsMap { + results[sourceID] = s.LookupNeighborsAt(sourceID, query) } - return results } -/* - Filter neighbors from a single route server. -*/ -func (self *NeighborsStore) FilterNeighborsAt( +// FilterNeighborsAt filters neighbors from a single route server. +func (s *NeighborsStore) FilterNeighborsAt( sourceID string, filter *api.NeighborFilter, ) api.Neighbors { results := []*api.Neighbor{} - - self.RLock() - neighbors := self.neighborsMap[sourceID] - self.RUnlock() + s.RLock() + neighbors := s.neighborsMap[sourceID] + s.RUnlock() // Apply filters for _, neighbor := range neighbors { @@ -284,54 +284,54 @@ func (self *NeighborsStore) FilterNeighborsAt( return results } -/* - Filter neighbors by name or by ASN. - Collect results from all routeservers. -*/ -func (self *NeighborsStore) FilterNeighbors( +// FilterNeighbors retrieves neighbors by name or by ASN +// from all route servers. +func (s *NeighborsStore) FilterNeighbors( filter *api.NeighborFilter, ) api.Neighbors { results := []*api.Neighbor{} - // Get neighbors from all routeservers - for sourceID, _ := range self.neighborsMap { - rsResults := self.FilterNeighborsAt(sourceID, filter) + for sourceID := range s.neighborsMap { + rsResults := s.FilterNeighborsAt(sourceID, filter) results = append(results, rsResults...) } - return results } -// Build some stats for monitoring -func (self *NeighborsStore) Stats() NeighborsStoreStats { +// Stats exports some statistics for monitoring. +func (s *NeighborsStore) Stats() NeighborsStoreStats { totalNeighbors := 0 rsStats := []RouteServerNeighborsStats{} - self.RLock() - for sourceID, neighbors := range self.neighborsMap { - status := self.statusMap[sourceID] + s.RLock() + for sourceID, neighbors := range s.neighborsMap { + status := s.statusMap[sourceID] totalNeighbors += len(neighbors) serverStats := RouteServerNeighborsStats{ - Name: self.cfgMap[sourceID].Name, - State: stateToString(status.State), + Name: s.cfgMap[sourceID].Name, + State: stateToString(status.State), Neighbors: len(neighbors), - UpdatedAt: status.LastRefresh, + UpdatedAt: status.LastRefresh, } rsStats = append(rsStats, serverStats) } - self.RUnlock() + s.RUnlock() storeStats := NeighborsStoreStats{ TotalNeighbors: totalNeighbors, - RouteServers: rsStats, + RouteServers: rsStats, } return storeStats } -func (self *NeighborsStore) CachedAt() time.Time { - return self.lastRefresh +// CachedAt returns the last time the store content +// was refreshed. +func (s *NeighborsStore) CachedAt() time.Time { + return s.lastRefresh } -func (self *NeighborsStore) CacheTTL() time.Time { - return self.lastRefresh.Add(self.refreshInterval) +// CacheTTL returns the next time when a refresh +// will be started. +func (s *NeighborsStore) CacheTTL() time.Time { + return s.lastRefresh.Add(s.refreshInterval) } diff --git a/pkg/store/neighbors_store_test.go b/pkg/store/neighbors_store_test.go index f1999f1..d7f13a1 100644 --- a/pkg/store/neighbors_store_test.go +++ b/pkg/store/neighbors_store_test.go @@ -7,10 +7,7 @@ import ( "github.com/alice-lg/alice-lg/pkg/api" ) - -/* - Make a store and populate it with data -*/ +// Make a store and populate it with data func makeTestNeighborsStore() *NeighborsStore { // Populate neighbors diff --git a/pkg/store/routes_store.go b/pkg/store/routes_store.go index ec046ff..ab09521 100644 --- a/pkg/store/routes_store.go +++ b/pkg/store/routes_store.go @@ -1,6 +1,7 @@ package store import ( + "context" "log" "strings" "sync" @@ -16,12 +17,12 @@ import ( type RoutesStore struct { routesMap map[string]*api.RoutesResponse statusMap map[string]StoreStatus - cfgMap map[string]*config.SourceConfig + cfgMap map[string]*config.SourceConfig refreshInterval time.Duration lastRefresh time.Time - neighborsStore *NeighborsStore + neighborsStore *NeighborsStore sync.RWMutex } @@ -29,10 +30,9 @@ type RoutesStore struct { // NewRoutesStore makes a new store instance // with a cfg. func NewRoutesStore( - neighborsStore *NeighborsStore, - cfg *config.Config, + neighborsStore *NeighborsStore, + cfg *config.Config, ) *RoutesStore { - // Build mapping based on source instances routesMap := make(map[string]*api.RoutesResponse) statusMap := make(map[string]StoreStatus) @@ -55,19 +55,18 @@ func NewRoutesStore( if refreshInterval == 0 { refreshInterval = time.Duration(5) * time.Minute } - store := &RoutesStore{ routesMap: routesMap, statusMap: statusMap, - cfgMap: cfgMap, + cfgMap: cfgMap, refreshInterval: refreshInterval, - neighborsStore: neighborsStore, + neighborsStore: neighborsStore, } return store } // Start starts the routes store -func (rs *RoutesStore) Start() { +func (rs *RoutesStore) Start(ctx context.Context) { log.Println("Starting local routes store") log.Println("Routes Store refresh interval set to:", rs.refreshInterval) if err := rs.init(); err != nil { @@ -208,7 +207,7 @@ func (rs *RoutesStore) CacheTTL() time.Time { // Lookup routes transform func routeToLookupRoute( - nStore *NeighborsStore, + nStore *NeighborsStore, source *config.SourceConfig, state string, route *api.Route, @@ -216,9 +215,9 @@ func routeToLookupRoute( // Get neighbor and make route neighbor := nStore.GetNeighborAt(source.ID, route.NeighborID) lookup := &api.LookupRoute{ - Route: route, - State: state, - Neighbor: neighbor, + Route: route, + State: state, + Neighbor: neighbor, RouteServer: &api.RouteServer{ ID: source.ID, Name: source.Name, @@ -229,7 +228,7 @@ func routeToLookupRoute( // Routes filter func filterRoutesByPrefix( - nStore *NeighborsStore, + nStore *NeighborsStore, source *config.SourceConfig, routes api.Routes, prefix string, @@ -247,7 +246,7 @@ func filterRoutesByPrefix( } func filterRoutesByNeighborIDs( - nStore *NeighborsStore, + nStore *NeighborsStore, source *config.SourceConfig, routes api.Routes, neighborIDs []string, @@ -280,13 +279,13 @@ func (rs *RoutesStore) LookupNeighborsPrefixesAt( rs.RUnlock() filtered := filterRoutesByNeighborIDs( - rs.neighborsStore, + rs.neighborsStore, source, routes.Filtered, neighborIDs, "filtered") imported := filterRoutesByNeighborIDs( - rs.neighborsStore, + rs.neighborsStore, source, routes.Imported, neighborIDs, @@ -316,13 +315,13 @@ func (rs *RoutesStore) LookupPrefixAt( rs.RUnlock() filtered := filterRoutesByPrefix( - rs.neighborsStore, + rs.neighborsStore, cfg, routes.Filtered, prefix, "filtered") imported := filterRoutesByPrefix( - rs.neighborsStore, + rs.neighborsStore, cfg, routes.Imported, prefix,