Merge remote-tracking branch 'upstream/develop' into gobgp_integration

This commit is contained in:
Emil Palm 2019-02-21 15:30:49 +01:00
commit 166a3f0fcf
23 changed files with 2313 additions and 8598 deletions

3
.gitignore vendored
View File

@ -28,9 +28,8 @@ DIST/
var/ var/
etc/alicelg/alice.conf etc/alice-lg/alice.conf
.DS_Store .DS_Store
*coverage* *coverage*

View File

@ -10,7 +10,7 @@ ARCH=amd64
SYSTEM_INIT=systemd SYSTEM_INIT=systemd
# == END BUILD CONFIGURATION == # == END BUILD CONFIGURATION ==
VERSION=$(shell cat ./VERSION) VERSION=$(shell cat ./VERSION)
@ -45,16 +45,16 @@ dev:
backend_prod: client_prod backend_prod: client_prod
$(MAKE) -C backend/ bundle $(MAKE) -C backend/ bundle
$(MAKE) -C backend/ linux $(MAKE) -C backend/ linux
alice: client_prod backend_prod alice: client_prod backend_prod
mv backend/alice-lg-* bin/ mv backend/alice-lg-* bin/
dist: clean alice dist: clean alice
mkdir -p $(DIST)opt/ecix/alicelg/bin mkdir -p $(DIST)opt/alice-lg/alice-lg/bin
mkdir -p $(DIST)etc/alicelg mkdir -p $(DIST)etc/alice-lg
# Adding post install script # Adding post install script
cp install/scripts/after_install $(DIST)/. cp install/scripts/after_install $(DIST)/.
@ -70,10 +70,10 @@ else
endif endif
# Copy example configuration # Copy example configuration
cp etc/alicelg/alice.example.conf $(DIST)/etc/alicelg/alice.example.conf cp etc/alice-lg/alice.example.conf $(DIST)/etc/alice-lg/alice.example.conf
# Copy application # Copy application
cp bin/$(PROG)-linux-$(ARCH) DIST/opt/ecix/alicelg/bin/. cp bin/$(PROG)-linux-$(ARCH) DIST/opt/ecix/alice-lg/bin/.
rpm: dist rpm: dist
@ -84,7 +84,7 @@ rpm: dist
# Create RPM from dist # Create RPM from dist
fpm -s dir -t rpm -n $(PROG) -v $(VERSION) -C $(DIST) \ fpm -s dir -t rpm -n $(PROG) -v $(VERSION) -C $(DIST) \
--architecture $(ARCH) \ --architecture $(ARCH) \
--config-files /etc/alicelg/alice.example.conf \ --config-files /etc/alice-lg/alice.example.conf \
--after-install $(DIST)/after_install \ --after-install $(DIST)/after_install \
opt/ etc/ opt/ etc/
@ -105,7 +105,7 @@ remote_rpm: build_server dist
scp -r $(DIST) $(BUILD_SERVER):$(REMOTE_DIST) scp -r $(DIST) $(BUILD_SERVER):$(REMOTE_DIST)
ssh $(BUILD_SERVER) -- fpm -s dir -t rpm -n $(PROG) -v $(VERSION) -C $(REMOTE_DIST) \ ssh $(BUILD_SERVER) -- fpm -s dir -t rpm -n $(PROG) -v $(VERSION) -C $(REMOTE_DIST) \
--architecture $(ARCH) \ --architecture $(ARCH) \
--config-files /etc/alicelg/alice.example.conf \ --config-files /etc/alice-lg/alice.example.conf \
--after-install $(REMOTE_DIST)/after_install \ --after-install $(REMOTE_DIST)/after_install \
opt/ etc/ opt/ etc/

View File

@ -3,7 +3,7 @@ __"No, no! The adventures first, explanations take such a dreadful time."__
_Lewis Carroll, Alice's Adventures in Wonderland & Through the Looking-Glass_ _Lewis Carroll, Alice's Adventures in Wonderland & Through the Looking-Glass_
Take a look at an Alice-LG production examples at: Take a look at an Alice-LG production examples at:
- https://lg-beta.de-cix.net/ - https://lg.de-cix.net/
- https://lg.ecix.net/ - https://lg.ecix.net/
And checkout the API at: And checkout the API at:
@ -23,7 +23,7 @@ Currently Alice-LG supports the following APIs:
Normally you would first install the [birdwatcher API](https://github.com/ecix/birdwatcher) directly on the machine(s) where you run [BIRD](http://bird.network.cz/) on Normally you would first install the [birdwatcher API](https://github.com/ecix/birdwatcher) directly on the machine(s) where you run [BIRD](http://bird.network.cz/) on
and then install Alice-LG on a seperate public facing server and point her to the afore mentioned [birdwatcher API](https://github.com/ecix/birdwatcher). and then install Alice-LG on a seperate public facing server and point her to the afore mentioned [birdwatcher API](https://github.com/ecix/birdwatcher).
This project was a direct result of the [RIPE IXP Tools Hackathon](https://atlas.ripe.net/hackathon/ixp-tools/) This project was a direct result of the [RIPE IXP Tools Hackathon](https://atlas.ripe.net/hackathon/ixp-tools/)
just prior to [RIPE73](https://ripe73.ripe.net/) in Madrid, Spain. just prior to [RIPE73](https://ripe73.ripe.net/) in Madrid, Spain.
Major thanks to Barry O'Donovan who built the original [INEX Bird's Eye](https://github.com/inex/birdseye) BIRD API of which Alice-LG is a spinnoff Major thanks to Barry O'Donovan who built the original [INEX Bird's Eye](https://github.com/inex/birdseye) BIRD API of which Alice-LG is a spinnoff
@ -63,17 +63,17 @@ Your Alice-LG source will now be located at `~/go/src/github.com/alice-lg/alice-
## Configuration ## Configuration
An example configuration can be found at An example configuration can be found at
[etc/alicelg/alice.example.conf](https://github.com/ecix/alice-lg/blob/readme_update/etc/alicelg/alice.example.conf). [etc/alice-lg/alice.example.conf](https://github.com/alice-lg/alice-lg/blob/readme_update/etc/alice-lg/alice.example.conf).
You can copy it to any of the following locations: You can copy it to any of the following locations:
etc/alicelg/alice.conf # local etc/alice-lg/alice.conf # local
etc/alicelg/alice.local.conf # local etc/alice-lg/alice.local.conf # local
/etc/alicelg/alice.conf # global /etc/alice-lg/alice.conf # global
You will have to edit the configuration file as you need to point Alice-LG to the correct [APIs](https://github.com/ecix/birdwatcher): You will have to edit the configuration file as you need to point Alice-LG to the correct [APIs](https://github.com/alice-lg/birdwatcher):
```ini ```ini
[source.0] [source.0]
@ -127,15 +127,15 @@ In your alice.conf, you now can specify a theme by setting:
with the optional parameter (the "mountpoint" of the theme) with the optional parameter (the "mountpoint" of the theme)
url_base = /theme url_base = /theme
You can put assets (images, fonts, javscript, css) in
You can put assets (images, fonts, javscript, css) in
this folder. this folder.
Stylesheets and Javascripts are automatically included in Stylesheets and Javascripts are automatically included in
the client's html and are served from the backend. the client's html and are served from the backend.
Alice provides early stages of an extension API, which is for now Alice provides early stages of an extension API, which is for now
only used to modify the content of the welcome screen, only used to modify the content of the welcome screen,
by providing a javascript in your theme containing: by providing a javascript in your theme containing:
@ -146,7 +146,7 @@ Alice.updateContent({
tagline: "powered by Alice" tagline: "powered by Alice"
} }
}); });
``` ```
For an example check out: https://github.com/alice-lg/alice-theme-example For an example check out: https://github.com/alice-lg/alice-theme-example
@ -154,7 +154,7 @@ For an example check out: https://github.com/alice-lg/alice-theme-example
## Hacking ## Hacking
The client is a Single Page React Application. The client is a Single Page React Application.
All sources are available in `client/`. All sources are available in `client/`.
Install build tools as needed: Install build tools as needed:

View File

@ -1 +1 @@
3.4.2 3.4.4

View File

@ -51,9 +51,9 @@ func endpoint(wrapped apiEndpoint) httprouter.Handle {
} }
// Make error response // Make error response
result = apiErrorResponse(rsId, err) result, status := apiErrorResponse(rsId, err)
payload, _ := json.Marshal(result) payload, _ := json.Marshal(result)
http.Error(res, string(payload), http.StatusInternalServerError) http.Error(res, string(payload), status)
return return
} }

View File

@ -20,7 +20,12 @@ func apiStatus(_req *http.Request, params httprouter.Params) (api.Response, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
source := AliceConfig.SourceById(rsId).getInstance()
source := AliceConfig.SourceInstanceById(rsId)
if source == nil {
return nil, SOURCE_NOT_FOUND_ERROR
}
result, err := source.Status() result, err := source.Status()
if err != nil { if err != nil {
apiLogSourceError("status", rsId, err) apiLogSourceError("status", rsId, err)

View File

@ -37,7 +37,11 @@ func apiNeighborsList(_req *http.Request, params httprouter.Params) (api.Respons
Neighbours: neighbors, Neighbours: neighbors,
} }
} else { } else {
source := AliceConfig.SourceById(rsId).getInstance() source := AliceConfig.SourceInstanceById(rsId)
if source == nil {
return nil, SOURCE_NOT_FOUND_ERROR
}
neighborsResponse, err = source.Neighbours() neighborsResponse, err = source.Neighbours()
if err != nil { if err != nil {
apiLogSourceError("neighbors", rsId, err) apiLogSourceError("neighbors", rsId, err)

View File

@ -15,7 +15,12 @@ func apiRoutesList(_req *http.Request, params httprouter.Params) (api.Response,
return nil, err return nil, err
} }
neighborId := params.ByName("neighborId") neighborId := params.ByName("neighborId")
source := AliceConfig.SourceById(rsId).getInstance()
source := AliceConfig.SourceInstanceById(rsId)
if source == nil {
return nil, SOURCE_NOT_FOUND_ERROR
}
result, err := source.Routes(neighborId) result, err := source.Routes(neighborId)
if err != nil { if err != nil {
apiLogSourceError("routes", rsId, neighborId, err) apiLogSourceError("routes", rsId, neighborId, err)
@ -38,7 +43,11 @@ func apiRoutesListReceived(
} }
neighborId := params.ByName("neighborId") neighborId := params.ByName("neighborId")
source := AliceConfig.SourceById(rsId).getInstance() source := AliceConfig.SourceInstanceById(rsId)
if source == nil {
return nil, SOURCE_NOT_FOUND_ERROR
}
result, err := source.RoutesReceived(neighborId) result, err := source.RoutesReceived(neighborId)
if err != nil { if err != nil {
apiLogSourceError("routes_received", rsId, neighborId, err) apiLogSourceError("routes_received", rsId, neighborId, err)
@ -107,7 +116,11 @@ func apiRoutesListFiltered(
} }
neighborId := params.ByName("neighborId") neighborId := params.ByName("neighborId")
source := AliceConfig.SourceById(rsId).getInstance() source := AliceConfig.SourceInstanceById(rsId)
if source == nil {
return nil, SOURCE_NOT_FOUND_ERROR
}
result, err := source.RoutesFiltered(neighborId) result, err := source.RoutesFiltered(neighborId)
if err != nil { if err != nil {
apiLogSourceError("routes_filtered", rsId, neighborId, err) apiLogSourceError("routes_filtered", rsId, neighborId, err)
@ -176,7 +189,11 @@ func apiRoutesListNotExported(
} }
neighborId := params.ByName("neighborId") neighborId := params.ByName("neighborId")
source := AliceConfig.SourceById(rsId).getInstance() source := AliceConfig.SourceInstanceById(rsId)
if source == nil {
return nil, SOURCE_NOT_FOUND_ERROR
}
result, err := source.RoutesNotExported(neighborId) result, err := source.RoutesNotExported(neighborId)
if err != nil { if err != nil {
apiLogSourceError("routes_not_exported", rsId, neighborId, err) apiLogSourceError("routes_not_exported", rsId, neighborId, err)

View File

@ -6,30 +6,51 @@ package main
// to internal IP addresses. // to internal IP addresses.
import ( import (
"net/http"
"net/url" "net/url"
"strings" "strings"
"github.com/alice-lg/alice-lg/backend/api" "github.com/alice-lg/alice-lg/backend/api"
) )
type ResourceNotFoundError struct{}
func (self *ResourceNotFoundError) Error() string {
return "resource not found"
}
var SOURCE_NOT_FOUND_ERROR = &ResourceNotFoundError{}
const ( const (
GENERIC_ERROR_TAG = "GENERIC_ERROR" GENERIC_ERROR_TAG = "GENERIC_ERROR"
CONNECTION_REFUSED_TAG = "CONNECTION_REFUSED" CONNECTION_REFUSED_TAG = "CONNECTION_REFUSED"
CONNECTION_TIMEOUT_TAG = "CONNECTION_TIMEOUT" CONNECTION_TIMEOUT_TAG = "CONNECTION_TIMEOUT"
RESOURCE_NOT_FOUND_TAG = "NOT_FOUND"
) )
const ( const (
GENERIC_ERROR_CODE = 42 GENERIC_ERROR_CODE = 42
CONNECTION_REFUSED_CODE = 100 CONNECTION_REFUSED_CODE = 100
CONNECTION_TIMEOUT_CODE = 101 CONNECTION_TIMEOUT_CODE = 101
RESOURCE_NOT_FOUND_CODE = 404
) )
func apiErrorResponse(routeserverId string, err error) api.ErrorResponse { const (
ERROR_STATUS = http.StatusInternalServerError
RESOURCE_NOT_FOUND_STATUS = http.StatusNotFound
)
func apiErrorResponse(routeserverId string, err error) (api.ErrorResponse, int) {
code := GENERIC_ERROR_CODE code := GENERIC_ERROR_CODE
message := err.Error() message := err.Error()
tag := GENERIC_ERROR_TAG tag := GENERIC_ERROR_TAG
status := ERROR_STATUS
switch e := err.(type) { switch e := err.(type) {
case *ResourceNotFoundError:
tag = RESOURCE_NOT_FOUND_TAG
code = RESOURCE_NOT_FOUND_CODE
status = RESOURCE_NOT_FOUND_STATUS
case *url.Error: case *url.Error:
if strings.Contains(message, "connection refused") { if strings.Contains(message, "connection refused") {
tag = CONNECTION_REFUSED_TAG tag = CONNECTION_REFUSED_TAG
@ -47,5 +68,5 @@ func apiErrorResponse(routeserverId string, err error) api.ErrorResponse {
Tag: tag, Tag: tag,
Message: message, Message: message,
RouteserverId: routeserverId, RouteserverId: routeserverId,
} }, status
} }

View File

@ -9,7 +9,7 @@ import (
// Helper: Validate source Id // Helper: Validate source Id
func validateSourceId(id string) (string, error) { func validateSourceId(id string) (string, error) {
if len(id) > 15 { if len(id) > 42 {
return "unknown", fmt.Errorf("Source ID too long with length: %d", len(id)) return "unknown", fmt.Errorf("Source ID too long with length: %d", len(id))
} }
return id, nil return id, nil

View File

@ -115,6 +115,17 @@ func (self *Config) SourceById(sourceId string) *SourceConfig {
return nil return nil
} }
// Get instance by id
func (self *Config) SourceInstanceById(sourceId string) sources.Source {
sourceConfig := self.SourceById(sourceId)
if sourceConfig == nil {
return nil // Nothing to do here.
}
// Get instance from config
return sourceConfig.getInstance()
}
// Get sources keys form ini // Get sources keys form ini
func getSourcesKeys(config *ini.File) []string { func getSourcesKeys(config *ini.File) []string {
sources := []string{} sources := []string{}
@ -624,9 +635,9 @@ func getSources(config *ini.File) ([]*SourceConfig, error) {
// Try to load configfiles as specified in the files // Try to load configfiles as specified in the files
// list. For example: // list. For example:
// //
// ./etc/alicelg/alice.conf // ./etc/alice-lg/alice.conf
// /etc/alicelg/alice.conf // /etc/alice-lg/alice.conf
// ./etc/alicelg/alice.local.conf // ./etc/alice-lg/alice.local.conf
// //
func loadConfig(file string) (*Config, error) { func loadConfig(file string) (*Config, error) {

View File

@ -9,7 +9,7 @@ import (
func TestLoadConfigs(t *testing.T) { func TestLoadConfigs(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
} }
@ -39,7 +39,7 @@ func TestLoadConfigs(t *testing.T) {
func TestSourceConfigDefaultsOverride(t *testing.T) { func TestSourceConfigDefaultsOverride(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
} }
@ -70,7 +70,7 @@ func TestSourceConfigDefaultsOverride(t *testing.T) {
} }
func TestRejectAndNoexportReasons(t *testing.T) { func TestRejectAndNoexportReasons(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
} }
@ -97,7 +97,7 @@ func TestRejectAndNoexportReasons(t *testing.T) {
} }
func TestBlackholeParsing(t *testing.T) { func TestBlackholeParsing(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
} }
@ -116,7 +116,7 @@ func TestBlackholeParsing(t *testing.T) {
} }
func TestOwnASN(t *testing.T) { func TestOwnASN(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
} }
@ -127,7 +127,7 @@ func TestOwnASN(t *testing.T) {
} }
func TestRpkiConfig(t *testing.T) { func TestRpkiConfig(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
} }
@ -157,7 +157,7 @@ func TestRpkiConfig(t *testing.T) {
} }
func TestRejectCandidatesConfig(t *testing.T) { func TestRejectCandidatesConfig(t *testing.T) {
config, err := loadConfig("../etc/alicelg/alice.example.conf") config, err := loadConfig("../etc/alice-lg/alice.example.conf")
if err != nil { if err != nil {
t.Error("Could not load test config:", err) t.Error("Could not load test config:", err)
return return

View File

@ -17,7 +17,7 @@ func main() {
// Handle commandline parameters // Handle commandline parameters
configFilenameFlag := flag.String( configFilenameFlag := flag.String(
"config", "/etc/alicelg/alice.conf", "config", "/etc/alice-lg/alice.conf",
"Alice looking glass configuration file", "Alice looking glass configuration file",
) )

View File

@ -119,6 +119,10 @@ func parseBirdwatcherStatus(bird ClientResponse, config Config) (api.Status, err
config.Timezone, config.Timezone,
) )
if config.ShowLastReboot == false {
lastReboot = time.Time{}
}
lastReconfig, _ := parseServerTime( lastReconfig, _ := parseServerTime(
birdStatus["last_reconfig"], birdStatus["last_reconfig"],
config.ServerTimeExt, config.ServerTimeExt,

View File

@ -4,9 +4,9 @@
# Use node:10 as base image # Use node:10 as base image
# #
FROM node:10 FROM node:11
RUN npm install -g gulp RUN npm install -g gulp@4.0.0
RUN npm install -g gulp-cli RUN npm install -g gulp-cli

View File

@ -14,7 +14,7 @@ image:
docker build . -t $(DOCKER_IMAGE) docker build . -t $(DOCKER_IMAGE)
deps: image deps: image
$(DOCKER_EXEC) "npm install" $(DOCKER_EXEC) "yarn install"
client: stop deps client: stop deps
@echo "Building alice UI" @echo "Building alice UI"

View File

@ -175,22 +175,26 @@ class RoutesPage extends React.Component {
<RoutesViewEmpty routes={this.props.routes} <RoutesViewEmpty routes={this.props.routes}
loadNotExported={this.props.loadNotExported} /> loadNotExported={this.props.loadNotExported} />
<RoutesView <RoutesView
type={ROUTES_FILTERED} type={ROUTES_FILTERED}
routeserverId={this.props.params.routeserverId} routeserverId={this.props.params.routeserverId}
protocolId={this.props.params.protocolId} /> protocolId={this.props.params.protocolId} />
{this.props.receivedLoading && <RoutesLoadingIndicator />}
<RoutesView <RoutesView
type={ROUTES_RECEIVED} type={ROUTES_RECEIVED}
routeserverId={this.props.params.routeserverId} routeserverId={this.props.params.routeserverId}
protocolId={this.props.params.protocolId} /> protocolId={this.props.params.protocolId} />
{this.props.notExportedLoading && <RoutesLoadingIndicator />}
<RoutesView <RoutesView
type={ROUTES_NOT_EXPORTED} type={ROUTES_NOT_EXPORTED}
routeserverId={this.props.params.routeserverId} routeserverId={this.props.params.routeserverId}
protocolId={this.props.params.protocolId} /> protocolId={this.props.params.protocolId} />
<RoutesLoadingIndicator />
</div> </div>
<div className="col-lg-3 col-md-12 col-aside-details"> <div className="col-lg-3 col-md-12 col-aside-details">
@ -290,7 +294,12 @@ export default connect(
filtersApplied: filtersApplied, filtersApplied: filtersApplied,
}, },
relatedPeers: relatedPeers relatedPeers: relatedPeers,
// Loding indicator helper
receivedLoading: state.routes.receivedLoading,
filteredLoading: state.routes.filteredLoading,
notExportedLoading: state.routes.notExportedLoading
}); });
} }
)(RoutesPage); )(RoutesPage);

7639
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,11 @@
# Create the required user and set permissions # Create the required user and set permissions
# #
SERVICE=alicelg SERVICE=alice-lg
echo "[i] Post install $SERVICE" echo "[i] Post install $SERVICE"
echo "[i] Creating user and updating permissions" echo "[i] Creating user and updating permissions"
useradd --system -d /opt/ecix/$SERVICE $SERVICE useradd --system -d /opt/alice-lg/$SERVICE $SERVICE
echo "[i] Fixing permissions" echo "[i] Fixing permissions"
chown -R $SERVICE:$SERVICE /opt/ecix/$SERVICE chown -R $SERVICE:$SERVICE /opt/alice-lg/$SERVICE

View File

@ -5,8 +5,8 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
User=alicelg User=alice-lg
ExecStart=/opt/ecix/alicelg/bin/alice-lg-linux-amd64 ExecStart=/opt/alice-lg/alice-lg/bin/alice-lg-linux-amd64
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -1,5 +1,5 @@
# Alice Looking Glass # Alice Looking Glass
description "Alice Looking Glass" description "Alice Looking Glass"
author "Matthias Hannig <mha@ecix.net>" author "Matthias Hannig <mha@ecix.net>"
@ -10,5 +10,4 @@ respawn limit 20 10
start on runlevel [2345] start on runlevel [2345]
stop on runlevel [!2345] stop on runlevel [!2345]
exec su -l alicelg -c /opt/ecix/alicelg/bin/alice-lg-linux-amd64 2>&1 | logger -i -t 'ALICE LG' exec su -l alice-lg -c /opt/alice-lg/alice-lg/bin/alice-lg-linux-amd64 2>&1 | logger -i -t 'ALICE LG'