refactored responses and http server

This commit is contained in:
Annika Hannig 2021-10-27 17:54:51 +00:00
parent aa9292b74c
commit 330047fac1
No known key found for this signature in database
GPG Key ID: 62E226E47DDCE58D
29 changed files with 324 additions and 215 deletions

View File

@ -8,7 +8,9 @@ import (
// A Response is a general API response. All API responses
// contain meta information with API version and caching
// information.
type Response interface{}
type Response struct {
Meta *Meta `json:"api"`
}
// Details are usually the original backend response
type Details map[string]interface{}
@ -101,8 +103,8 @@ type Status struct {
// StatusResponse ??
type StatusResponse struct {
Response
Status Status `json:"status"`
Meta *Meta `json:"api"`
}
// A RouteServer is a datasource with attributes.

View File

@ -89,15 +89,15 @@ func (n *Neighbor) MatchName(name string) bool {
// A NeighborsResponse is a list of neighbors with
// caching information.
type NeighborsResponse struct {
Response
Neighbors Neighbors `json:"neighbors"`
Meta *Meta `json:"api"`
}
// CacheTTL returns the duration of validity
// of the neighbor response.
func (res *NeighborsResponse) CacheTTL() time.Duration {
now := time.Now().UTC()
return res.Meta.TTL.Sub(now)
return res.Response.Meta.TTL.Sub(now)
}
// NeighborsLookupResults is a mapping of lookup neighbors.
@ -130,6 +130,6 @@ func (neighbors NeighborsStatus) Swap(i, j int) {
// NeighborsStatusResponse contains the status of all neighbors
// on a RS.
type NeighborsStatusResponse struct {
Response
Neighbors NeighborsStatus `json:"neighbors"`
Meta *Meta `json:"api"`
}

View File

@ -69,7 +69,7 @@ func (routes Routes) Swap(i, j int) {
// RoutesResponse contains all routes from a source
type RoutesResponse struct {
Meta *Meta `json:api`
Response
Imported Routes `json:"imported"`
Filtered Routes `json:"filtered"`
NotExported Routes `json:"not_exported"`
@ -78,11 +78,11 @@ type RoutesResponse struct {
// CacheTTL returns the cache ttl of the reponse
func (res *RoutesResponse) CacheTTL() time.Duration {
now := time.Now().UTC()
return res.Meta.TTL.Sub(now)
return res.Response.Meta.TTL.Sub(now)
}
// Timed responses include the duration of the request
type Timed struct {
// TimedResponse include the duration of the request
type TimedResponse struct {
RequestDuration float64 `json:"request_duration_ms"`
}
@ -95,13 +95,13 @@ type Pagination struct {
TotalResults int `json:"total_results"`
}
// A Paginated response with pagination info
type Paginated struct {
// A PaginatedResponse with pagination info
type PaginatedResponse struct {
Pagination Pagination `json:"pagination"`
}
// Searchable responses include filters applied and available
type Searchable struct {
// FilteredResponse includes filters applied and available
type FilteredResponse struct {
FiltersAvailable *SearchFilters `json:"filters_available"`
FiltersApplied *SearchFilters `json:"filters_applied"`
}
@ -157,33 +157,49 @@ func (r LookupRoutes) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}
// RoutesLookup contains routes and pagination info
type RoutesLookup struct {
Routes LookupRoutes `json:"routes"`
Pagination Pagination `json:"pagination"`
}
// RoutesLookupResponse is a PaginatedResponse with
// a set of lookup routes, as the result of a query of
// a specific route server.
type RoutesLookupResponse struct {
Paginated
Timed
Searchable
Response
PaginatedResponse
TimedResponse
FilteredResponse
Routes LookupRoutes `json:"routes"`
Meta *Meta `json:"api"`
}
// GlobalRoutesLookupResponse is the result of a routes
// query across all route servers.
type GlobalRoutesLookupResponse struct {
Response
Paginated
Timed
Searchable
PaginatedResponse
TimedResponse
FilteredResponse
Routes LookupRoutes `json:"routes"`
}
// A PaginatedRoutesResponse includes routes and pagination
// information form a single route server
type PaginatedRoutesResponse struct {
Response
PaginatedResponse
TimedResponse
FilteredResponse
RoutesResponse
}
// A PaginatedRoutesLookupResponse TODO
type PaginatedRoutesLookupResponse struct {
Response
Timed
Searchable
TimedResponse
FilteredResponse
Imported *RoutesLookupResponse `json:"imported"`
Filtered *RoutesLookupResponse `json:"filtered"`
Imported *RoutesLookup `json:"imported"`
Filtered *RoutesLookup `json:"filtered"`
}

View File

@ -10,11 +10,13 @@ func TestStatusResponseSerialization(t *testing.T) {
// Make status
response := StatusResponse{
Meta: &Meta{
Version: "2.0.0",
CacheStatus: CacheStatus{},
ResultFromCache: false,
TTL: time.Now(),
Response: Response{
Meta: &Meta{
Version: "2.0.0",
CacheStatus: CacheStatus{},
ResultFromCache: false,
TTL: time.Now(),
},
},
Status: Status{
Message: "Server is up and running",

View File

@ -8,9 +8,6 @@ import (
"strings"
"github.com/julienschmidt/httprouter"
"github.com/alice-lg/alice-lg/pkg/api"
"github.com/alice-lg/alice-lg/pkg/config"
)
// Alice LG Rest API
@ -33,7 +30,9 @@ import (
// LookupPrefix /api/v1/lookup/prefix?q=<prefix>
// LookupNeighbor /api/v1/lookup/neighbor?asn=1235
type apiEndpoint func(*http.Request, httprouter.Params) (api.Response, error)
type response interface{}
type apiEndpoint func(*http.Request, httprouter.Params) (response, error)
// Wrap handler for access controll, throtteling and compression
func endpoint(wrapped apiEndpoint) httprouter.Handle {
@ -86,7 +85,6 @@ func endpoint(wrapped apiEndpoint) httprouter.Handle {
// Register api endpoints
func (s *Server) apiRegisterEndpoints(
cfg *config.Config,
router *httprouter.Router,
) error {
@ -111,7 +109,7 @@ func (s *Server) apiRegisterEndpoints(
endpoint(s.apiRoutesListNotExported))
// Querying
if cfg.Server.EnablePrefixLookup == true {
if s.cfg.Server.EnablePrefixLookup == true {
router.GET("/api/v1/lookup/prefix",
endpoint(s.apiLookupPrefixGlobal))
router.GET("/api/v1/lookup/neighbors",

View File

@ -13,7 +13,7 @@ import (
func (s *Server) apiStatusShow(
_req *http.Request,
_params httprouter.Params,
) (api.Response, error) {
) (response, error) {
status, err := CollectAppStatus(s.routesStore, s.neighborsStore)
return status, err
}
@ -22,7 +22,7 @@ func (s *Server) apiStatusShow(
func (s *Server) apiStatus(
_req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
rsID, err := validateSourceID(params.ByName("id"))
if err != nil {
return nil, err
@ -35,7 +35,7 @@ func (s *Server) apiStatus(
result, err := source.Status()
if err != nil {
apiLogSourceError("status", rsID, err)
s.logSourceError("status", rsID, err)
}
return result, err
@ -45,7 +45,7 @@ func (s *Server) apiStatus(
func (s *Server) apiConfigShow(
_req *http.Request,
_params httprouter.Params,
) (api.Response, error) {
) (response, error) {
result := api.ConfigResponse{
Asn: s.cfg.Server.Asn,
BGPCommunities: s.cfg.UI.BGPCommunities,

View File

@ -15,7 +15,7 @@ import (
func (s *Server) apiNeighborsList(
_req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
rsID, err := validateSourceID(params.ByName("id"))
if err != nil {
return nil, err
@ -30,26 +30,28 @@ func (s *Server) apiNeighborsList(
neighbors := s.neighborsStore.GetNeighborsAt(rsID)
// Make response
neighborsResponse = &api.NeighborsResponse{
Meta: &api.Meta{
Version: config.Version,
CacheStatus: api.CacheStatus{
OrigTTL: 0,
CachedAt: sourceStatus.LastRefresh,
Response: api.Response{
Meta: &api.Meta{
Version: config.Version,
CacheStatus: api.CacheStatus{
OrigTTL: 0,
CachedAt: sourceStatus.LastRefresh,
},
ResultFromCache: true, // you bet!
TTL: sourceStatus.LastRefresh.Add(
s.neighborsStore.RefreshInterval),
},
ResultFromCache: true, // you bet!
TTL: sourceStatus.LastRefresh.Add(
s.neighborsStore.RefreshInterval),
},
Neighbors: neighbors,
}
} else {
source := s.neighborsStore.SourceInstanceByID(rsID)
source := s.cfg.SourceInstanceByID(rsID)
if source == nil {
return nil, ErrSourceNotFound
}
neighborsResponse, err = source.Neighbors()
if err != nil {
apiLogSourceError("neighbors", rsID, err)
s.logSourceError("neighbors", rsID, err)
return nil, err
}
}

View File

@ -13,7 +13,7 @@ import (
func (s *Server) apiRoutesList(
_req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
rsID, err := validateSourceID(params.ByName("id"))
if err != nil {
return nil, err
@ -27,7 +27,7 @@ func (s *Server) apiRoutesList(
result, err := source.Routes(neighborID)
if err != nil {
apiLogSourceError("routes", rsID, neighborID, err)
s.logSourceError("routes", rsID, neighborID, err)
}
return result, err
@ -37,7 +37,7 @@ func (s *Server) apiRoutesList(
func (s *Server) apiRoutesListReceived(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
// Measure response time
t0 := time.Now()
@ -54,7 +54,7 @@ func (s *Server) apiRoutesListReceived(
result, err := source.RoutesReceived(neighborID)
if err != nil {
apiLogSourceError("routes_received", rsID, neighborID, err)
s.logSourceError("routes_received", rsID, neighborID, err)
return nil, err
}
@ -91,18 +91,22 @@ func (s *Server) apiRoutesListReceived(
// Make paginated response
response := api.PaginatedRoutesResponse{
RoutesResponse: &api.RoutesResponse{
Api: result.Api,
RoutesResponse: api.RoutesResponse{
Response: api.Response{
Meta: result.Response.Meta,
},
Imported: routes,
},
TimedResponse: api.TimedResponse{
RequestDuration: DurationMs(queryDuration),
},
FilterableResponse: api.FilterableResponse{
FilteredResponse: api.FilteredResponse{
FiltersAvailable: filtersAvailable,
FiltersApplied: filtersApplied,
},
Pagination: pagination,
PaginatedResponse: api.PaginatedResponse{
Pagination: pagination,
},
}
return response, nil
@ -111,7 +115,7 @@ func (s *Server) apiRoutesListReceived(
func (s *Server) apiRoutesListFiltered(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
t0 := time.Now()
rsID, err := validateSourceID(params.ByName("id"))
@ -127,7 +131,7 @@ func (s *Server) apiRoutesListFiltered(
result, err := source.RoutesFiltered(neighborID)
if err != nil {
apiLogSourceError("routes_filtered", rsID, neighborID, err)
s.logSourceError("routes_filtered", rsID, neighborID, err)
return nil, err
}
@ -164,18 +168,22 @@ func (s *Server) apiRoutesListFiltered(
// Make response
response := api.PaginatedRoutesResponse{
RoutesResponse: &api.RoutesResponse{
Api: result.Api,
RoutesResponse: api.RoutesResponse{
Response: api.Response{
Meta: result.Response.Meta,
},
Filtered: routes,
},
TimedResponse: api.TimedResponse{
RequestDuration: DurationMs(queryDuration),
},
FilterableResponse: api.FilterableResponse{
FilteredResponse: api.FilteredResponse{
FiltersAvailable: filtersAvailable,
FiltersApplied: filtersApplied,
},
Pagination: pagination,
PaginatedResponse: api.PaginatedResponse{
Pagination: pagination,
},
}
return response, nil
@ -184,7 +192,7 @@ func (s *Server) apiRoutesListFiltered(
func (s *Server) apiRoutesListNotExported(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
t0 := time.Now()
rsID, err := validateSourceID(params.ByName("id"))
@ -200,7 +208,7 @@ func (s *Server) apiRoutesListNotExported(
result, err := source.RoutesNotExported(neighborID)
if err != nil {
apiLogSourceError("routes_not_exported", rsID, neighborID, err)
s.logSourceError("routes_not_exported", rsID, neighborID, err)
return nil, err
}
@ -237,18 +245,22 @@ func (s *Server) apiRoutesListNotExported(
// Make response
response := api.PaginatedRoutesResponse{
RoutesResponse: &api.RoutesResponse{
Api: result.Api,
RoutesResponse: api.RoutesResponse{
Response: api.Response{
Meta: result.Response.Meta,
},
NotExported: routes,
},
TimedResponse: api.TimedResponse{
RequestDuration: DurationMs(queryDuration),
},
FilterableResponse: api.FilterableResponse{
FilteredResponse: api.FilteredResponse{
FiltersAvailable: filtersAvailable,
FiltersApplied: filtersApplied,
},
Pagination: pagination,
PaginatedResponse: api.PaginatedResponse{
Pagination: pagination,
},
}
return response, nil

View File

@ -13,14 +13,14 @@ import (
func (s *Server) apiRouteServersList(
_req *http.Request,
_params httprouter.Params,
) (api.Response, error) {
) (response, error) {
// Get list of sources from config,
routeservers := api.RouteServers{}
sources := s.cfg.Sources
for _, source := range sources {
routeservers = append(routeservers, api.RouteServer{
Id: source.ID,
ID: source.ID,
Type: source.Type,
Name: source.Name,
Group: source.Group,

View File

@ -15,7 +15,7 @@ import (
func (s *Server) apiLookupPrefixGlobal(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
// TODO: This function is way too long
// Get prefix to query
@ -106,29 +106,27 @@ func (s *Server) apiLookupPrefixGlobal(
// Make response
response := api.PaginatedRoutesLookupResponse{
Api: api.ApiStatus{
CacheStatus: api.CacheStatus{
CachedAt: AliceRoutesStore.CachedAt(),
Response: api.Response{
Meta: &api.Meta{
CacheStatus: api.CacheStatus{
CachedAt: s.routesStore.CachedAt(),
},
ResultFromCache: true, // Well.
TTL: s.routesStore.CacheTTL(),
},
ResultFromCache: true, // Well.
Ttl: AliceRoutesStore.CacheTTL(),
},
TimedResponse: api.TimedResponse{
RequestDuration: DurationMs(queryDuration),
},
Imported: &api.LookupRoutesResponse{
Routes: routesImported,
PaginatedResponse: &api.PaginatedResponse{
Pagination: paginationImported,
},
Imported: &api.RoutesLookup{
Routes: routesImported,
Pagination: paginationImported,
},
Filtered: &api.LookupRoutesResponse{
Routes: routesFiltered,
PaginatedResponse: &api.PaginatedResponse{
Pagination: paginationFiltered,
},
Filtered: &api.RoutesLookup{
Routes: routesFiltered,
Pagination: paginationFiltered,
},
FilterableResponse: api.FilterableResponse{
FilteredResponse: api.FilteredResponse{
FiltersAvailable: filtersAvailable,
FiltersApplied: filtersApplied,
},
@ -140,7 +138,7 @@ func (s *Server) apiLookupPrefixGlobal(
func (s *Server) apiLookupNeighborsGlobal(
req *http.Request,
params httprouter.Params,
) (api.Response, error) {
) (response, error) {
// Query neighbors store
filter := api.NeighborFilterFromQuery(req.URL.Query())
neighbors := s.neighborsStore.FilterNeighbors(filter)
@ -149,12 +147,14 @@ func (s *Server) apiLookupNeighborsGlobal(
// Make response
response := &api.NeighborsResponse{
Api: api.ApiStatus{
CacheStatus: api.CacheStatus{
CachedAt: s.neighborsStore.CachedAt(),
Response: api.Response{
Meta: &api.Meta{
CacheStatus: api.CacheStatus{
CachedAt: s.neighborsStore.CachedAt(),
},
ResultFromCache: true, // You would not have guessed.
TTL: s.neighborsStore.CacheTTL(),
},
ResultFromCache: true, // You would not have guessed.
Ttl: s.neighborsStore.CacheTTL(),
},
Neighbors: neighbors,
}

View File

@ -60,7 +60,7 @@ func apiErrorResponse(
status := StatusError
switch e := err.(type) {
case *ResourceNotFoundError:
case *ErrResourceNotFoundError:
tag = TagResourceNotFound
code = CodeResourceNotFound
status = StatusResourceNotFound

View File

@ -1,25 +0,0 @@
package http
import (
"fmt"
"testing"
)
func TestApiLogSourceError(t *testing.T) {
err := fmt.Errorf("an unexpected error occured")
conf := &Config{
Sources: []*SourceConfig{
&SourceConfig{
ID: "rs1v4",
Name: "rs1.example.net (IPv4)",
},
},
}
AliceConfig = conf
apiLogSourceError("foo.bar", "rs1v4", 23, "Test")
apiLogSourceError("foo.bam", "rs1v4", err)
apiLogSourceError("foo.baz", "rs1v4", 23, 42, "foo", err)
}

View File

@ -8,16 +8,16 @@ import (
func TestApiRoutesPagination(t *testing.T) {
routes := api.Routes{
&api.Route{Id: "r01"},
&api.Route{Id: "r02"},
&api.Route{Id: "r03"},
&api.Route{Id: "r04"},
&api.Route{Id: "r05"},
&api.Route{Id: "r06"},
&api.Route{Id: "r07"},
&api.Route{Id: "r08"},
&api.Route{Id: "r09"},
&api.Route{Id: "r10"},
&api.Route{ID: "r01"},
&api.Route{ID: "r02"},
&api.Route{ID: "r03"},
&api.Route{ID: "r04"},
&api.Route{ID: "r05"},
&api.Route{ID: "r06"},
&api.Route{ID: "r07"},
&api.Route{ID: "r08"},
&api.Route{ID: "r09"},
&api.Route{ID: "r10"},
}
paginated, pagination := apiPaginateRoutes(routes, 0, 8)
@ -36,13 +36,13 @@ func TestApiRoutesPagination(t *testing.T) {
// Check paginated slicing
r := paginated[0]
if r.Id != "r01" {
t.Error("First route on page 0 should be r01, got:", r.Id)
if r.ID != "r01" {
t.Error("First route on page 0 should be r01, got:", r.ID)
}
r = paginated[len(paginated)-1]
if r.Id != "r08" {
t.Error("Last route should be r08, but got:", r.Id)
if r.ID != "r08" {
t.Error("Last route should be r08, but got:", r.ID)
}
// Second page
@ -52,13 +52,13 @@ func TestApiRoutesPagination(t *testing.T) {
}
r = paginated[0]
if r.Id != "r09" {
t.Error("First route on page 1 should be r09, got:", r.Id)
if r.ID != "r09" {
t.Error("First route on page 1 should be r09, got:", r.ID)
}
r = paginated[len(paginated)-1]
if r.Id != "r10" {
t.Error("Last route should be r10, but got:", r.Id)
if r.ID != "r10" {
t.Error("Last route should be r10, but got:", r.ID)
}
// Access out of bound page

View File

@ -19,20 +19,20 @@ func makeQueryRequest(q string) *http.Request {
func makeQueryRoutes() api.Routes {
routes := api.Routes{
&api.Route{
Id: "route_01",
NeighborId: "n01",
ID: "route_01",
NeighborID: "n01",
Network: "123.42.43.0/24",
Gateway: "23.42.42.1",
},
&api.Route{
Id: "route_02",
NeighborId: "n01",
ID: "route_02",
NeighborID: "n01",
Network: "142.23.0.0/16",
Gateway: "42.42.42.1",
},
&api.Route{
Id: "route_03",
NeighborId: "n01",
ID: "route_03",
NeighborID: "n01",
Network: "123.43.0.0/16",
Gateway: "23.42.43.1",
},
@ -54,11 +54,11 @@ func TestApiQueryFilterNextHopGateway(t *testing.T) {
}
// Check presence of route_01 and _03, matching prefix 123.
if filtered[0].Id != "route_01" {
t.Error("Expected route_01, got:", filtered[0].Id)
if filtered[0].ID != "route_01" {
t.Error("Expected route_01, got:", filtered[0].ID)
}
if filtered[1].Id != "route_03" {
t.Error("Expected route_03, got:", filtered[1].Id)
if filtered[1].ID != "route_03" {
t.Error("Expected route_03, got:", filtered[1].ID)
}
// Test another query matching the gateway only
@ -71,7 +71,7 @@ func TestApiQueryFilterNextHopGateway(t *testing.T) {
t.Error("Expected only one result")
}
if filtered[0].Id != "route_02" {
if filtered[0].ID != "route_02" {
t.Error("Expected route_02 to match criteria, got:", filtered[0])
}
}

View File

@ -7,12 +7,16 @@ import (
)
// Log an api error
func apiLogSourceError(module string, sourceID string, params ...interface{}) {
func (s *Server) logSourceError(
module string,
sourceID string,
params ...interface{},
) {
var err error
args := []string{}
// Get source configuration
source := AliceConfig.SourceByID(sourceID)
source := s.cfg.SourceByID(sourceID)
sourceName := "unknown"
if source != nil {
sourceName = source.Name

27
pkg/http/logging_test.go Normal file
View File

@ -0,0 +1,27 @@
package http
import (
"fmt"
"testing"
"github.com/alice-lg/alice-lg/pkg/config"
)
func TestApiLogSourceError(t *testing.T) {
err := fmt.Errorf("an unexpected error occured")
cfg := &config.Config{
Sources: []*config.SourceConfig{
&config.SourceConfig{
ID: "rs1v4",
Name: "rs1.example.net (IPv4)",
},
},
}
s := &Server{cfg: cfg}
s.logSourceError("foo.bar", "rs1v4", 23, "Test")
s.logSourceError("foo.bam", "rs1v4", err)
s.logSourceError("foo.baz", "rs1v4", 23, 42, "foo", err)
}

View File

@ -47,5 +47,5 @@ func (s *Server) Start() {
}
// Start http server
log.Fatal(http.ListenAndServe(cfg.Server.Listen, router))
log.Fatal(http.ListenAndServe(s.cfg.Server.Listen, router))
}

View File

@ -9,8 +9,8 @@ import (
// AppStatus contains application status information
type AppStatus struct {
Version string `json:"version"`
Routes api.RoutesStoreStats `json:"routes"`
Neighbors api.NeighborsStoreStats `json:"neighbors"`
Routes *api.RoutesStoreStats `json:"routes"`
Neighbors *api.NeighborsStoreStats `json:"neighbors"`
}
// CollectAppStatus initializes the application

View File

@ -25,21 +25,21 @@ import (
"path/filepath"
"github.com/julienschmidt/httprouter"
"github.com/alice-lg/alice-lg/pkg/config"
)
// Theme is a client customization through additional
// HTML, CSS and JS content.
type Theme struct {
Config ThemeConfig
Config config.ThemeConfig
}
// NewTheme creates a theme from a config
func NewTheme(config ThemeConfig) *Theme {
theme := &Theme{
func NewTheme(config config.ThemeConfig) *Theme {
return &Theme{
Config: config,
}
return theme
}
// Get includable files from theme directory

View File

@ -7,6 +7,8 @@ import (
"os"
"strings"
"testing"
"github.com/alice-lg/alice-lg/pkg/config"
)
func touchFile(path, filename string) error {
@ -27,7 +29,7 @@ func TestThemeFiles(t *testing.T) {
touchFile(themePath, "script.js")
// Load theme
theme := NewTheme(ThemeConfig{
theme := NewTheme(config.ThemeConfig{
BasePath: "/theme",
Path: themePath,
})
@ -64,7 +66,7 @@ func TestThemeIncludeHash(t *testing.T) {
// Create some "stylesheets" and a "script"
touchFile(themePath, "style.css")
theme := NewTheme(ThemeConfig{
theme := NewTheme(config.ThemeConfig{
BasePath: "/theme",
Path: themePath,
})
@ -91,28 +93,28 @@ func TestThemeIncludes(t *testing.T) {
touchFile(themePath, "script.js")
// Load theme
theme := NewTheme(ThemeConfig{
theme := NewTheme(config.ThemeConfig{
BasePath: "/theme",
Path: themePath,
})
stylesHtml := theme.StylesheetIncludes()
scriptsHtml := theme.ScriptIncludes()
stylesHTML := theme.StylesheetIncludes()
scriptsHTML := theme.ScriptIncludes()
if !strings.HasPrefix(scriptsHtml, "<script") {
if !strings.HasPrefix(scriptsHTML, "<script") {
t.Error("Script include should start with <script")
}
if strings.Index(scriptsHtml, "script.js") == -1 {
if strings.Index(scriptsHTML, "script.js") == -1 {
t.Error("Scripts include should contain script.js")
}
if !strings.HasPrefix(stylesHtml, "<link") {
if !strings.HasPrefix(stylesHTML, "<link") {
t.Error("Stylesheet include should start with <link")
}
if strings.Index(stylesHtml, "extra.css") == -1 {
if strings.Index(stylesHTML, "extra.css") == -1 {
t.Error("Stylesheet include should contain extra.css")
}
if strings.Index(stylesHtml, "script.js") != -1 {
if strings.Index(stylesHTML, "script.js") != -1 {
t.Error("Stylesheet include should not contain script.js")
}

10
pkg/http/timeconv.go Normal file
View File

@ -0,0 +1,10 @@
package http
import (
"time"
)
// DurationMs converts time.Duration to milliseconds
func DurationMs(d time.Duration) float64 {
return float64(d) / 1000.0 / 1000.0 // nano -> micro -> milli
}

12
pkg/http/timeconv_test.go Normal file
View File

@ -0,0 +1,12 @@
package http
import (
"testing"
"time"
)
func TestDurationMs(t *testing.T) {
if DurationMs(time.Second) != 1000 {
t.Error("duration ms should return the duration in milliseconds")
}
}

View File

@ -9,7 +9,6 @@ import (
"github.com/julienschmidt/httprouter"
"github.com/alice-lg/alice-lg/client"
"github.com/alice-lg/alice-lg/pkg/config"
)
// Web Client
@ -17,8 +16,8 @@ import (
// Prepare client HTML:
// Set paths and add version to assets.
func webPrepareClientHTML(html string) string {
status, _ := NewAppStatus()
func (s*Server)webPrepareClientHTML(html string) string {
status, _ := CollectAppStatus(s.routesStore, s.neighborsStore)
// Replace paths and tags
rewriter := strings.NewReplacer(
@ -35,7 +34,7 @@ func webPrepareClientHTML(html string) string {
// Register assets handler and index handler
// at /static and /
func webRegisterAssets(cfg *config.Config, router *httprouter.Router) error {
func (s *Server) webRegisterAssets(router *httprouter.Router) error {
log.Println("Preparing and installing assets")
// Prepare client html: Rewrite paths
@ -45,14 +44,14 @@ func webRegisterAssets(cfg *config.Config, router *httprouter.Router) error {
}
indexHTML := string(indexHTMLData) // TODO: migrate to []byte
theme := NewTheme(cfg.UI.Theme)
theme := NewTheme(s.cfg.UI.Theme)
err = theme.RegisterThemeAssets(router)
if err != nil {
log.Println("Warning:", err)
}
// Update paths
indexHTML = webPrepareClientHTML(indexHTML)
indexHTML = s.webPrepareClientHTML(indexHTML)
// Register static assets
router.Handler("GET", "/static/*path", client.AssetsHTTPHandler("/static"))

View File

@ -247,7 +247,9 @@ func (b *GenericBirdwatcher) Status() (*api.StatusResponse, error) {
}
response := &api.StatusResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Status: birdStatus,
}
@ -272,7 +274,9 @@ func (b *GenericBirdwatcher) NeighborsStatus() (
}
response := &api.NeighborsStatusResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Neighbors: neighbors,
}
return response, nil // dereference for now
@ -317,7 +321,9 @@ func (b *GenericBirdwatcher) LookupPrefix(
// Make result
response := &api.RoutesLookupResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Routes: results,
}
return response, nil

View File

@ -257,7 +257,9 @@ func (src *MultiTableBirdwatcher) fetchRequiredRoutes(
}
response = &api.RoutesResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Imported: importedRoutes,
Filtered: filteredRoutes,
}
@ -381,7 +383,9 @@ func (src *MultiTableBirdwatcher) Neighbors() (*api.NeighborsResponse, error) {
}
response = &api.NeighborsResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Neighbors: neighbors,
}
@ -410,7 +414,7 @@ func (src *MultiTableBirdwatcher) Routes(
return nil, err
}
response.Meta = required.Meta
response.Response.Meta = required.Meta
response.Imported = required.Imported
response.Filtered = required.Filtered
response.NotExported = notExported
@ -427,7 +431,7 @@ func (src *MultiTableBirdwatcher) RoutesReceived(
// Check if we have a cache hit
cachedRoutes := src.routesRequiredCache.Get(neighborID)
if cachedRoutes != nil {
response.Meta = cachedRoutes.Meta
response.Response.Meta = cachedRoutes.Response.Meta
response.Imported = cachedRoutes.Imported
return response, nil
}
@ -487,7 +491,9 @@ func (src *MultiTableBirdwatcher) RoutesNotExported(
}
response = &api.RoutesResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
NotExported: routes,
}
@ -519,7 +525,9 @@ func (src *MultiTableBirdwatcher) AllRoutes() (*api.RoutesResponse, error) {
}
response := &api.RoutesResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
}
// Parse the routes

View File

@ -134,7 +134,9 @@ func (src *SingleTableBirdwatcher) fetchRequiredRoutes(
}
response = &api.RoutesResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Imported: importedRoutes,
Filtered: filteredRoutes,
}
@ -172,7 +174,9 @@ func (src *SingleTableBirdwatcher) Neighbors() (*api.NeighborsResponse, error) {
}
response = &api.NeighborsResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
Neighbors: neighbors,
}
@ -279,7 +283,9 @@ func (src *SingleTableBirdwatcher) RoutesNotExported(
}
response = &api.RoutesResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
NotExported: routes,
}
@ -311,7 +317,9 @@ func (src *SingleTableBirdwatcher) AllRoutes() (*api.RoutesResponse, error) {
}
response := &api.RoutesResponse{
Meta: apiStatus,
Response: api.Response{
Meta: apiStatus,
},
}
// Parse the routes

View File

@ -111,7 +111,9 @@ func (src *BgplgdSource) makeResponseMeta() *api.Meta {
func (src *BgplgdSource) Status() (*api.StatusResponse, error) {
// Make API request and read response. We do not cache the result.
response := &api.StatusResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Status: api.Status{
Version: "openbgpd",
Message: "openbgpd up and running",
@ -160,7 +162,9 @@ func (src *BgplgdSource) Neighbors() (*api.NeighborsResponse, error) {
}
response = &api.NeighborsResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Neighbors: nb,
}
src.neighborsCache.Set(response)
@ -193,7 +197,9 @@ func (src *BgplgdSource) NeighborsStatus() (*api.NeighborsStatusResponse, error)
}
response := &api.NeighborsStatusResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Neighbors: nb,
}
return response, nil
@ -235,7 +241,9 @@ func (src *BgplgdSource) Routes(neighborID string) (*api.RoutesResponse, error)
rejected := filterRejectedRoutes(src.cfg.RejectCommunities, routes)
response = &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: received,
NotExported: api.Routes{},
Filtered: rejected,
@ -279,7 +287,9 @@ func (src *BgplgdSource) RoutesReceived(neighborID string) (*api.RoutesResponse,
received := filterReceivedRoutes(src.cfg.RejectCommunities, routes)
response = &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: received,
NotExported: api.Routes{},
Filtered: api.Routes{},
@ -323,7 +333,9 @@ func (src *BgplgdSource) RoutesFiltered(neighborID string) (*api.RoutesResponse,
rejected := filterRejectedRoutes(src.cfg.RejectCommunities, routes)
response = &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: api.Routes{},
NotExported: api.Routes{},
Filtered: rejected,
@ -337,7 +349,9 @@ func (src *BgplgdSource) RoutesFiltered(neighborID string) (*api.RoutesResponse,
// from the rs for a neighbor.
func (src *BgplgdSource) RoutesNotExported(neighborID string) (*api.RoutesResponse, error) {
response := &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: api.Routes{},
NotExported: api.Routes{},
Filtered: api.Routes{},
@ -374,7 +388,9 @@ func (src *BgplgdSource) AllRoutes() (*api.RoutesResponse, error) {
rejected := filterRejectedRoutes(src.cfg.RejectCommunities, routes)
response := &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: received,
NotExported: api.Routes{},
Filtered: rejected,

View File

@ -137,7 +137,9 @@ func (src *StateServerSource) Status() (*api.StatusResponse, error) {
}
status := decodeAPIStatus(body)
response := &api.StatusResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Status: status,
}
return response, nil
@ -148,7 +150,7 @@ func (src *StateServerSource) Neighbors() (*api.NeighborsResponse, error) {
// Query cache and see if we have a hit
response := src.neighborsCache.Get()
if response != nil {
response.Meta.ResultFromCache = true
response.Response.Meta.ResultFromCache = true
return response, nil
}
@ -183,7 +185,9 @@ func (src *StateServerSource) Neighbors() (*api.NeighborsResponse, error) {
}
response = &api.NeighborsResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Neighbors: nb,
}
src.neighborsCache.Set(response)
@ -216,7 +220,9 @@ func (src *StateServerSource) NeighborsStatus() (*api.NeighborsStatusResponse, e
}
response := &api.NeighborsStatusResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Neighbors: nb,
}
return response, nil
@ -227,7 +233,7 @@ func (src *StateServerSource) NeighborsStatus() (*api.NeighborsStatusResponse, e
func (src *StateServerSource) Routes(neighborID string) (*api.RoutesResponse, error) {
response := src.routesCache.Get(neighborID)
if response != nil {
response.Meta.ResultFromCache = true
response.Response.Meta.ResultFromCache = true
return response, nil
}
@ -258,7 +264,9 @@ func (src *StateServerSource) Routes(neighborID string) (*api.RoutesResponse, er
rejected := filterRejectedRoutes(src.cfg.RejectCommunities, routes)
response = &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: received,
NotExported: api.Routes{},
Filtered: rejected,
@ -272,7 +280,7 @@ func (src *StateServerSource) Routes(neighborID string) (*api.RoutesResponse, er
func (src *StateServerSource) RoutesReceived(neighborID string) (*api.RoutesResponse, error) {
response := src.routesReceivedCache.Get(neighborID)
if response != nil {
response.Meta.ResultFromCache = true
response.Response.Meta.ResultFromCache = true
return response, nil
}
@ -300,7 +308,9 @@ func (src *StateServerSource) RoutesReceived(neighborID string) (*api.RoutesResp
received := filterReceivedRoutes(src.cfg.RejectCommunities, routes)
response = &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: received,
NotExported: api.Routes{},
Filtered: api.Routes{},
@ -314,7 +324,7 @@ func (src *StateServerSource) RoutesReceived(neighborID string) (*api.RoutesResp
func (src *StateServerSource) RoutesFiltered(neighborID string) (*api.RoutesResponse, error) {
response := src.routesFilteredCache.Get(neighborID)
if response != nil {
response.Meta.ResultFromCache = true
response.Response.Meta.ResultFromCache = true
return response, nil
}
@ -342,7 +352,9 @@ func (src *StateServerSource) RoutesFiltered(neighborID string) (*api.RoutesResp
rejected := filterRejectedRoutes(src.cfg.RejectCommunities, routes)
response = &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: api.Routes{},
NotExported: api.Routes{},
Filtered: rejected,
@ -356,7 +368,9 @@ func (src *StateServerSource) RoutesFiltered(neighborID string) (*api.RoutesResp
// from the rs for a neighbor.
func (src *StateServerSource) RoutesNotExported(neighborID string) (*api.RoutesResponse, error) {
response := &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: api.Routes{},
NotExported: api.Routes{},
Filtered: api.Routes{},
@ -393,7 +407,9 @@ func (src *StateServerSource) AllRoutes() (*api.RoutesResponse, error) {
rejected := filterRejectedRoutes(src.cfg.RejectCommunities, routes)
response := &api.RoutesResponse{
Meta: src.makeResponseMeta(),
Response: api.Response{
Meta: src.makeResponseMeta(),
},
Imported: received,
NotExported: api.Routes{},
Filtered: rejected,

View File

@ -4,7 +4,6 @@ package store
import (
"strconv"
"strings"
"time"
)
// ContainsCi is like `strings.Contains` but case insensitive
@ -39,8 +38,3 @@ func SerializeReasons(reasons map[int]string) map[string]string {
}
return res
}
// DurationMs converts time.Duration to milliseconds
func DurationMs(d time.Duration) float64 {
return float64(d) / 1000.0 / 1000.0 // nano -> micro -> milli
}