Skip to content

Commit

Permalink
Merge branch '15-zettabgp-webapp' into 'main'
Browse files Browse the repository at this point in the history
Resolve "ZettaBGP WebApp"

Closes #15

See merge request imprj/01-bgp-testbed/zettabgp!13
  • Loading branch information
DlieBG committed Nov 16, 2024
2 parents 23ee9f5 + 8a5422f commit 2b6fc10
Show file tree
Hide file tree
Showing 62 changed files with 15,557 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/ui/
ui/dist/
ui/node_modules/
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mrt_library/
src/ui/
local/

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
15 changes: 15 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
FROM node:lts-alpine AS ui

WORKDIR /app

COPY ui/ .

RUN npm install
RUN npm run build


FROM python:3.10

WORKDIR /app/zettabgp

COPY requirements.txt .
RUN pip3 install -r requirements.txt

COPY --from=ui /app/dist/ui/browser/ /app/zettabgp/src/ui/
ENV ZETTABGP_WEBAPP_UI_PATH=/app/zettabgp/src/ui
ENV ZETTABGP_WEBAPP_APP=src.webapp:app
ENV ZETTABGP_WEBAPP_MRT_LIBRARY_PATH=mrt

COPY setup.py .
COPY src/ src/

Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ process zettabgp {
}
```

### Local MRT Library
For the WebApp you can provide a local MRT Library.\
This library contains several MRT Scenarios which define a predefined scenario for simulating MRT files with specific configurations.\
This library is managed via `scenario.json` files in the `mrt_library` directory.\
The syntax for the `scenario.json` files is the following.
```json
{
"name": "Test Scenario 1",
"description": "This is a test scenario.",
"no_rabbitmq_direct": false,
"rabbitmq_grouped": 5,
"no_mongodb_log": true,
"no_mongodb_state": true,
"no_mongodb_statistics": true,
"clear_mongodb": false,
"playback_speed": null,
"mrt_files": [
"20241005_0130_1728091800_bgp_lw_ixp_decix_update.bz2"
]
}
```

### Database and Message Queue
A sample setup for MongoDB and RabbitMQ is provided in the `db` directory.\
To run the docker compose setup run the following command in that directory.\
Expand All @@ -51,6 +73,13 @@ RABBIT_MQ_HOST
MONGO_DB_HOST
MONGO_DB_PORT
```
For internal configurations there are these variables too.\
But only overwrite them when you are debugging the respective parts locally.
```
ZETTABGP_WEBAPP_UI_PATH
ZETTABGP_WEBAPP_APP
ZETTABGP_WEBAPP_MRT_LIBRARY_PATH
```

## Usage
ZettaBGP provides a CLI interface with some commands for testbed simulations as well for production use.
Expand Down Expand Up @@ -132,6 +161,12 @@ zettabgp mrt-simulation <mrt-file> -o 10
```
Please keep in mind that most of the mrt files only contain a timeslot of 15 minutes.

#### `zettabgp webapp`
This command launches the debugging WebApp with MRT Library, Anomaly Explorer and the RabbitMQ interface.\
The WebApp will be exposed on port `8000` by default.\
You can access the ui using this link: http://127.0.0.1:8000/\
For the management of the MRT Library refer to this section above. [Local MRT Library](#local-mrt-library)

## Debugging
Some sample json messages for debugging purposes from ExaBGP can be found in the `samples` directory.

Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mrtparse
pydantic
pymongo
uvicorn
fastapi
exabgp
click
rich
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='ZettaBGP',
version='0.1.1',
version='0.2.1',
description='',
url='https://git.univ.leitwert.net/imprj/01-bgp-testbed/zettabgp',
author='Benedikt Schwering & Sebastian Forstner',
Expand All @@ -13,6 +13,8 @@
'mrtparse',
'pydantic',
'pymongo',
'uvicorn',
'fastapi',
'exabgp',
'click',
'rich',
Expand Down
Empty file added src/controllers/__init__.py
Empty file.
113 changes: 113 additions & 0 deletions src/controllers/mrt_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from src.models.mrt_library import MRTScenarioRequest, MRTScenarioResult, MRTScenario, MRTLibrary
from src.parsers.mrt_bgp4mp import MrtBgp4MpParser
from src.adapters.rabbitmq import RabbitMQAdapter
from src.adapters.mongodb import MongoDBAdapter
from src.models.route_update import ChangeType
from fastapi.exceptions import HTTPException
from datetime import datetime
from fastapi import APIRouter
from mrtparse import Reader
from pathlib import Path
import json, time, os

mrt_library_router = APIRouter()

def _get_mrt_library() -> MRTLibrary:
mrt_library = MRTLibrary(
scenarios=[],
)

for scenario_file in Path(
os.getenv('ZETTABGP_WEBAPP_MRT_LIBRARY_PATH', 'mrt_library')
).glob('**/scenario.json'):
with open(scenario_file, 'r') as file:
scenario = json.loads(file.read())
scenario['id'] = str(scenario_file.parent.absolute()).replace('/', '-')
scenario['path'] = str(scenario_file.parent.absolute())

mrt_library.scenarios.append(
MRTScenario.model_validate(
obj=scenario,
)
)

return mrt_library

def _get_mrt_scenario(id: str) -> MRTScenario:
for scenario in _get_mrt_library().scenarios:
if scenario.id == id:
return scenario

@mrt_library_router.get('/')
def get_mrt_library() -> MRTLibrary:
return _get_mrt_library()

@mrt_library_router.post('/')
def start_mrt_scenario(mrt_scenario_request: MRTScenarioRequest) -> MRTScenarioResult:
scenario = _get_mrt_scenario(
id=mrt_scenario_request.id,
)

if not scenario:
return HTTPException(
status_code=400,
detail='Scenario not found.',
)

mrt_scenario_result = MRTScenarioResult(
count_announce=0,
count_withdraw=0,
)

parser = MrtBgp4MpParser()

if not scenario.no_rabbitmq_direct or scenario.rabbitmq_grouped:
RabbitMQAdapter(
parser=parser,
no_direct=scenario.no_rabbitmq_direct,
queue_interval=scenario.rabbitmq_grouped,
)

if not scenario.no_mongodb_log or not scenario.no_mongodb_state or not scenario.no_mongodb_statistics:
MongoDBAdapter(
parser=parser,
no_mongodb_log=scenario.no_mongodb_log,
no_mongodb_state=scenario.no_mongodb_state,
no_mongodb_statistics=scenario.no_mongodb_statistics,
clear_mongodb=scenario.clear_mongodb,
)

playback_speed_reference: datetime = None

for mrt_file in scenario.mrt_files:
mrt_file = str(Path(scenario.path) / Path(mrt_file))

for message in Reader(mrt_file):
if message.data['type'] != {16: 'BGP4MP'}:
print('[dark_orange]\[WARN][/] Skipping unsupported MRT type: ', end='')
print(message.data['type'])
continue

current_timestamp: datetime = datetime.fromtimestamp(
timestamp=list(message.data['timestamp'].keys())[0],
)

if scenario.playback_speed:
if playback_speed_reference:
time.sleep((current_timestamp - playback_speed_reference).seconds / scenario.playback_speed)

playback_speed_reference = current_timestamp

updates = parser.parse(
bgp4mp_message=message,
)

if updates:
for update in updates:
match update.change_type:
case ChangeType.ANNOUNCE:
mrt_scenario_result.count_announce += 1
case ChangeType.WITHDRAW:
mrt_scenario_result.count_withdraw += 1

return mrt_scenario_result
8 changes: 8 additions & 0 deletions src/controllers/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from fastapi import APIRouter
import pkg_resources

version_router = APIRouter()

@version_router.get('/')
def get_version():
return pkg_resources.get_distribution('zettabgp').version
15 changes: 15 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from src.adapters.mongodb import MongoDBAdapter
from src.parsers.exabgp import ExaBGPParser
from datetime import timedelta, datetime
from src.webapp import start_webapp
from mrtparse import Reader
import click, time, sys
from rich import print
Expand Down Expand Up @@ -195,3 +196,17 @@ def mrt_simulation(no_rabbitmq_direct: bool, rabbitmq_grouped: int, no_mongodb_l
parser.parse(
bgp4mp_message=message,
)

@cli.command(
name='webapp',
help='Open the admin webapp.',
)
@click.option(
'--reload',
'-r',
is_flag=True,
)
def webapp(reload: bool):
start_webapp(
reload=reload,
)
26 changes: 26 additions & 0 deletions src/models/mrt_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from pydantic import BaseModel
from typing import Optional

class MRTScenarioRequest(BaseModel):
id: str

class MRTScenarioResult(BaseModel):
count_announce: int
count_withdraw: int

class MRTScenario(BaseModel):
id: str
path: str
name: str
description: str
no_rabbitmq_direct: bool
rabbitmq_grouped: Optional[int]
no_mongodb_log: bool
no_mongodb_state: bool
no_mongodb_statistics: bool
clear_mongodb: bool
playback_speed: Optional[int]
mrt_files: list[str]

class MRTLibrary(BaseModel):
scenarios: list[MRTScenario]
41 changes: 41 additions & 0 deletions src/webapp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from src.controllers.mrt_library import mrt_library_router
from src.controllers.version import version_router
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi import FastAPI
import uvicorn, os

app = FastAPI()

app.include_router(
router=mrt_library_router,
prefix='/api/mrt-library'
)
app.include_router(
router=version_router,
prefix='/api/version'
)

app.mount(
path='/ui',
app=StaticFiles(
directory=os.getenv('ZETTABGP_WEBAPP_UI_PATH', 'src/ui'),
),
name="ui",
)

@app.get('/')
def serve_angular_root():
return FileResponse(os.getenv('ZETTABGP_WEBAPP_UI_PATH', 'src/ui') + '/index.html')

@app.exception_handler(404)
def serve_angular_root(_, __):
return FileResponse(os.getenv('ZETTABGP_WEBAPP_UI_PATH', 'src/ui') + '/index.html')

def start_webapp(reload: bool):
uvicorn.run(
app=os.getenv('ZETTABGP_WEBAPP_APP', 'src.webapp:app'),
host='0.0.0.0',
port=8000,
reload=reload,
)
17 changes: 17 additions & 0 deletions ui/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false

[*.md]
max_line_length = off
trim_trailing_whitespace = false
42 changes: 42 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.

# Compiled output
/dist
/tmp
/out-tsc
/bazel-out

# Node
/node_modules
npm-debug.log
yarn-error.log

# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings

# System files
.DS_Store
Thumbs.db
Loading

0 comments on commit 2b6fc10

Please sign in to comment.