Compare commits

..

No commits in common. "5b5593b6cc16c39aac5d9eeca3e47a2ddf7aabd3" and "b480615ca27a60d0867f8eec710dedc9451080d8" have entirely different histories.

17 changed files with 114 additions and 449 deletions

View File

@ -1,14 +1,6 @@
# OpenBMP docker files # OpenBMP docker files
Docker files for OpenBMP. Docker files for OpenBMP.
> **Start here:**
> - **Deploying the stack?** Jump to [Greenfield deploy](#greenfield-deploy-recommended-deploysh)
> below, then see the [docs index](docs/README.md) for deployment types,
> portability findings, sizing, and backup.
> - **Configuring routers to feed it?** Go straight to
> [docs/router-integration.md](docs/router-integration.md) and the
> copy-paste fragments in [router-blueprints/](router-blueprints/).
## (Prerequisite) Platform Docker Install ## (Prerequisite) Platform Docker Install
> Ignore this step if you already have a current docker install > Ignore this step if you already have a current docker install
@ -112,10 +104,8 @@ sudo chmod -R 7777 $OBMP_DATA_ROOT
> refuse to start. Skip `postgres/` (setup.sh does this for you — see > refuse to start. Skip `postgres/` (setup.sh does this for you — see
> [docs/PORTABILITY-FINDINGS.md](docs/PORTABILITY-FINDINGS.md) finding 10). > [docs/PORTABILITY-FINDINGS.md](docs/PORTABILITY-FINDINGS.md) finding 10).
> DB tables are created **automatically** by psql-app on its first run (it > In order to init the DB tables, you must create the file ```${OBMP_DATA_ROOT}/config/init_db```. This should
> drops a `config/do_not_init_db` marker afterward so restarts skip the > only be done once or whenever you want to completely wipe out the DB and start over.
> migration). No `init_db` trigger file is needed — that was upstream
> behavior this repo's psql-app replaces.
Change ```OBMP_DATA_ROOT=<path>``` to where you created the directories above. The default is ```/var/openbmp``` Change ```OBMP_DATA_ROOT=<path>``` to where you created the directories above. The default is ```/var/openbmp```

View File

@ -130,11 +130,12 @@ configuration:
! !
bmp server 1 bmp server 1
host 10.40.40.202 port 1790 host 10.40.40.202 port 1790
description OpenBMP-Collector description OpenBMP
update-source Gi0/0/0/0 update-source Gi0/0/0/0
initial-delay 60 flapping-delay 60
initial-delay 5
stats-reporting-period 300 stats-reporting-period 300
initial-refresh delay 60 spread 30 initial-refresh delay 30 spread 2
! !
ssh server v2 ssh server v2
end end

270
deploy.sh
View File

@ -17,22 +17,14 @@
# ./deploy.sh # interactive; auto-detect + prompts # ./deploy.sh # interactive; auto-detect + prompts
# ./deploy.sh --wsl | --prod # force host type (still prompts rest) # ./deploy.sh --wsl | --prod # force host type (still prompts rest)
# ./deploy.sh --host-ip 10.0.0.50 # preset HOST_IP (internal stack IP) # ./deploy.sh --host-ip 10.0.0.50 # preset HOST_IP (internal stack IP)
# ./deploy.sh --router-ip 10.0.0.9 --router-port 1790 # BMP-facing target # ./deploy.sh --router-ip 10.0.0.9 --router-port 5000 # BMP-facing target
# ./deploy.sh --auth local|authelia # ./deploy.sh --auth local|authelia
# ./deploy.sh --scope full-stack|remote|central-store # ./deploy.sh --scope core|feeders|full|standalone
# ./deploy.sh --central-kafka 10.0.0.50:9092 # for --scope remote # ./deploy.sh --central-kafka 10.0.0.50:9092 # for --scope standalone
# ./deploy.sh --reset # wipe data tree first (guarded on prod) # ./deploy.sh --reset # wipe data tree first (guarded on prod)
# ./deploy.sh --install-prereqs # allow installing docker-ce unattended
# ./deploy.sh --prod --yes # unattended (needs enough presets) # ./deploy.sh --prod --yes # unattended (needs enough presets)
# REPO_DIR=/opt/obmp-docker ./deploy.sh # REPO_DIR=/opt/obmp-docker ./deploy.sh
# #
# ./deploy.sh teardown # stop + remove EVERYTHING, leave it down
# --wipe-data # also wipe $OBMP_DATA_ROOT (guarded;
# # backups/ preserved by default)
# --purge-images # also remove stack images
# --purge-backups # do not preserve backups/ on wipe
# --yes # unattended (requires explicit flags)
#
# SCOPE / DEPLOYMENT LEVELS (what each includes, excludes, ballpark sysreqs): # SCOPE / DEPLOYMENT LEVELS (what each includes, excludes, ballpark sysreqs):
# #
# core Collector core: zookeeper, kafka, psql, collector, psql-app, # core Collector core: zookeeper, kafka, psql, collector, psql-app,
@ -111,7 +103,7 @@
# Mitigations: NVMe with real IOPS; BMP-monitor pre-policy on the RRs only # Mitigations: NVMe with real IOPS; BMP-monitor pre-policy on the RRs only
# (not every client session) to avoid ingesting N reflected copies of the # (not every client session) to avoid ingesting N reflected copies of the
# same table; stagger connects (initial-refresh delay/spread); raise # same table; stagger connects (initial-refresh delay/spread); raise
# KAFKA/PSQL_APP mem; or split ingest with --scope remote collectors. # KAFKA/PSQL_APP mem; or split ingest with --scope standalone collectors.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# #
# HOST TYPE affects default HOST_IP source, default auth/scope, reset guarding, # HOST TYPE affects default HOST_IP source, default auth/scope, reset guarding,
@ -127,19 +119,9 @@ ARG_ROUTER_IP=""
ARG_ROUTER_PORT="" ARG_ROUTER_PORT=""
ARG_AUTH="" # local | authelia ARG_AUTH="" # local | authelia
ARG_SCOPE="" # full-stack | remote | central-store (aliases accepted) ARG_SCOPE="" # full-stack | remote | central-store (aliases accepted)
ARG_CENTRAL_KAFKA="" # host:port for --scope remote ARG_CENTRAL_KAFKA="" # host:port for --scope standalone
ARG_RESET=0 ARG_RESET=0
ARG_YES=0 ARG_YES=0
ARG_INSTALL_PREREQS=0
COMMAND="deploy" # deploy (default) | teardown
ARG_WIPE_DATA=0 # teardown: also wipe $OBMP_DATA_ROOT
ARG_PURGE_IMAGES=0 # teardown: also remove stack images
ARG_PURGE_BACKUPS=0 # teardown: do NOT preserve $OBMP_DATA_ROOT/backups
# First positional arg may be a subcommand.
case "${1:-}" in
teardown) COMMAND="teardown"; shift ;;
esac
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
@ -158,10 +140,6 @@ while [ $# -gt 0 ]; do
--central-kafka) ARG_CENTRAL_KAFKA="${2:?--central-kafka needs host:port}"; shift ;; --central-kafka) ARG_CENTRAL_KAFKA="${2:?--central-kafka needs host:port}"; shift ;;
--central-kafka=*) ARG_CENTRAL_KAFKA="${1#*=}" ;; --central-kafka=*) ARG_CENTRAL_KAFKA="${1#*=}" ;;
--reset) ARG_RESET=1 ;; --reset) ARG_RESET=1 ;;
--install-prereqs) ARG_INSTALL_PREREQS=1 ;;
--wipe-data) ARG_WIPE_DATA=1 ;;
--purge-images) ARG_PURGE_IMAGES=1 ;;
--purge-backups) ARG_PURGE_BACKUPS=1 ;;
--yes|-y) ARG_YES=1 ;; --yes|-y) ARG_YES=1 ;;
-h|--help) grep '^#' "$0" | sed 's/^#//'; exit 0 ;; -h|--help) grep '^#' "$0" | sed 's/^#//'; exit 0 ;;
*) echo "Unknown arg: $1" >&2; exit 1 ;; *) echo "Unknown arg: $1" >&2; exit 1 ;;
@ -169,9 +147,10 @@ while [ $# -gt 0 ]; do
shift shift
done done
# Shared output/prompt/.env helpers (log ok warn die hr banner step ask log() { printf '\033[1;36m[deploy]\033[0m %s\n' "$*"; }
# confirm get_env set_env is_ipv4) -- single definition for all deploy tooling. warn() { printf '\033[1;33m[deploy][warn]\033[0m %s\n' "$*" >&2; }
. "$REPO_DIR/scripts/deploy-lib.sh" die() { printf '\033[1;31m[deploy][err]\033[0m %s\n' "$*" >&2; exit 1; }
hr() { printf '\033[2m%s\033[0m\n' "----------------------------------------------------------------"; }
# Return the host-published TCP ports this deployment type will bind, so we can # Return the host-published TCP ports this deployment type will bind, so we can
# check them for collisions BEFORE bring-up. Ports come from docker-compose.yml # check them for collisions BEFORE bring-up. Ports come from docker-compose.yml
@ -246,71 +225,32 @@ preflight_ports() {
return $conflict return $conflict
} }
# Prompt with an editable default. Honors --yes (accepts default silently).
ask() {
local __var="$1" __prompt="$2" __default="${3:-}" __reply=""
if [ "$ARG_YES" -eq 1 ]; then
printf -v "$__var" '%s' "$__default"; return
fi
if [ -n "$__default" ]; then
read -r -p "$__prompt [$__default]: " __reply
__reply="${__reply:-$__default}"
else
read -r -p "$__prompt: " __reply
fi
printf -v "$__var" '%s' "$__reply"
}
confirm() {
local __prompt="$1" __reply=""
[ "$ARG_YES" -eq 1 ] && return 0
read -r -p "$__prompt [y/N]: " __reply
[ "$__reply" = "y" ] || [ "$__reply" = "Y" ]
}
cd "$REPO_DIR" || die "REPO_DIR '$REPO_DIR' not found" cd "$REPO_DIR" || die "REPO_DIR '$REPO_DIR' not found"
[ -f docker-compose.yml ] || die "no docker-compose.yml in $REPO_DIR - set REPO_DIR" [ -f docker-compose.yml ] || die "no docker-compose.yml in $REPO_DIR - set REPO_DIR"
command -v docker >/dev/null || die "docker not found"
# --- host prerequisites (fresh-WSL friendly) -------------------------------- docker compose version >/dev/null 2>&1 || die "docker compose v2 not available"
# A pristine WSL2 Ubuntu has neither docker-ce nor systemd enabled; check the
# whole chain and either remediate (--install-prereqs / interactive offer) or
# print the exact fix instead of dying with a bare message mid-run.
install_docker_ce() {
log "Installing docker-ce from the official Docker apt repo ..."
sudo apt-get update -qq
sudo apt-get install -y -qq ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
ok "docker-ce installed"
}
ensure_systemd_wsl() {
# Native docker in WSL2 needs systemd (the docker service). Pristine
# distros ship with it off; flipping it requires a WSL restart, which we
# cannot do from inside -- set it up and stop with clear instructions.
[ -d /run/systemd/system ] && return 0
grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null || return 0
warn "systemd is not running -- native docker cannot start without it."
if confirm "Enable systemd in /etc/wsl.conf now (needs a WSL restart afterwards)?"; then
if ! grep -q '^\[boot\]' /etc/wsl.conf 2>/dev/null; then
printf '\n[boot]\nsystemd=true\n' | sudo tee -a /etc/wsl.conf > /dev/null
elif ! grep -q '^systemd=true' /etc/wsl.conf; then
sudo sed -i '/^\[boot\]/a systemd=true' /etc/wsl.conf
fi
ok "systemd enabled in /etc/wsl.conf"
die "now run 'wsl --shutdown' from Windows, reopen this distro, and re-run ./deploy.sh"
fi
die "cannot continue without systemd on WSL (docker service will not start)"
}
preflight_host() {
if ! command -v docker >/dev/null; then
warn "docker is not installed on this host."
if [ "$ARG_INSTALL_PREREQS" -eq 1 ] || confirm "Install docker-ce now (official Docker apt repo)?"; then
ensure_systemd_wsl
install_docker_ce
sudo systemctl enable --now docker 2>/dev/null || true
else
die "install docker-ce first (https://docs.docker.com/engine/install/ubuntu/) or re-run with --install-prereqs"
fi
fi
docker compose version >/dev/null 2>&1 \
|| die "docker compose v2 missing - install the docker-compose-plugin package"
if ! docker info >/dev/null 2>&1; then
# client exists but daemon unreachable: systemd off (WSL) or service down
ensure_systemd_wsl
warn "docker daemon is not running - trying to start it ..."
sudo systemctl enable --now docker 2>/dev/null || true
sleep 2
docker info >/dev/null 2>&1 \
|| die "docker daemon still unreachable - check: systemctl status docker"
ok "docker daemon started"
fi
}
preflight_host
# --- Docker Desktop vs native-daemon guard ---------------------------------- # --- Docker Desktop vs native-daemon guard ----------------------------------
# On WSL, Docker Desktop runs the daemon in its OWN separate distro # On WSL, Docker Desktop runs the daemon in its OWN separate distro
@ -345,97 +285,23 @@ if detect_docker_desktop; then
fi fi
fi fi
# --- teardown subcommand ----------------------------------------------------- # --- .env helpers -----------------------------------------------------------
# Leave-it-down teardown: removes every container across ALL compose profiles,
# the named volumes, and the rendered gitignored configs. Data-tree wipe is
# opt-in (--wipe-data or interactive), preserving backups/ unless
# --purge-backups. Images are kept unless --purge-images. Prints the
# Windows-side cleanup it cannot reach from inside WSL.
ALL_PROFILES=(--profile test --profile auth --profile evpn-test --profile scale-out)
cmd_teardown() {
banner "OpenBMP stack teardown"
local dr; dr="$( [ -f .env ] && get_env OBMP_DATA_ROOT || true )"; dr="${dr:-/var/openbmp}"
echo
log "This will remove:"
echo " - all obmp containers (every profile: core, test, auth, evpn-test, scale-out)"
echo " - docker networks + named volumes (obmp_data-volume, obmp_ts-volume)"
echo " - rendered configs (gobgp/gobgpd.conf, gobgp-evpn/gobgpd.conf)"
[ "$ARG_PURGE_IMAGES" -eq 1 ] && echo " - stack images (--purge-images)"
if [ "$ARG_WIPE_DATA" -eq 1 ]; then
echo " - DATA TREE $dr (--wipe-data)$( [ "$ARG_PURGE_BACKUPS" -eq 1 ] && echo ' INCLUDING backups/' || echo ', preserving backups/')"
else
echo " - (data tree $dr is KEPT; add --wipe-data for a full wipe)"
fi
echo " - .env is always kept"
echo
confirm "Tear the stack down?" || die "aborted - nothing was removed"
log "Stopping and removing containers (all profiles) ..."
if [ "$ARG_PURGE_IMAGES" -eq 1 ]; then
docker compose "${ALL_PROFILES[@]}" down -v --remove-orphans --rmi all 2>&1 | tail -3 || true
else
docker compose "${ALL_PROFILES[@]}" down -v --remove-orphans 2>&1 | tail -3 || true
fi
docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true
ok "containers, networks, volumes removed"
rm -f gobgp/gobgpd.conf gobgp-evpn/gobgpd.conf 2>/dev/null || true
ok "rendered configs removed"
if [ "$ARG_WIPE_DATA" -eq 1 ] || { [ "$ARG_YES" -eq 0 ] && confirm "ALSO wipe the data tree $dr (Postgres history, Kafka spool)?"; }; then
if [ -d "$dr" ]; then
if grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then :; else
warn "Native host data wipe - this destroys all BMP history under $dr."
[ "$ARG_YES" -eq 1 ] && [ "$ARG_WIPE_DATA" -eq 0 ] && die "refusing implicit data wipe under --yes; pass --wipe-data explicitly"
if [ "$ARG_YES" -eq 0 ]; then
read -r -p "Type the data root path to confirm deletion: " __c
[ "$__c" = "$dr" ] || die "confirmation did not match - data tree untouched"
fi
fi
if [ "$ARG_PURGE_BACKUPS" -eq 1 ]; then
sudo rm -rf "${dr:?}"/* 2>/dev/null || true
ok "data tree wiped (including backups)"
else
sudo find "$dr" -mindepth 1 -maxdepth 1 ! -name backups -exec rm -rf {} + 2>/dev/null || true
ok "data tree wiped (backups/ preserved)"
fi
fi
fi
echo
hr
log "Linux-side teardown complete. Windows-side leftovers (if this was a"
log "WSL deployment) need an ELEVATED PowerShell:"
echo
echo " powershell -ExecutionPolicy Bypass -File scripts\\wsl-portproxy.ps1 -Remove"
echo
log "Verify clean: docker ps -a | grep obmp (expect nothing)"
hr
exit 0
}
[ "$COMMAND" = "teardown" ] && cmd_teardown
banner "OpenBMP stack deploy"
# --- .env bootstrap ----------------------------------------------------------
if [ ! -f .env ]; then if [ ! -f .env ]; then
[ -f .env.example ] || die "no .env or .env.example present" [ -f .env.example ] || die "no .env or .env.example present"
cp .env.example .env cp .env.example .env
log "Created .env from .env.example" log "Created .env from .env.example"
fi fi
get_env() { grep -E "^$1=" .env | head -1 | cut -d= -f2- || true; }
# Prime sudo early if filesystem setup will need it, so the password prompt set_env() {
# happens HERE (visible, at the start) instead of blocking mid-provision. local key="$1" val="$2"
_dr_probe="$(get_env OBMP_DATA_ROOT)"; _dr_probe="${_dr_probe:-/var/openbmp}" if grep -qE "^${key}=" .env; then
if [ ! -w "$(dirname "$_dr_probe")" ] || { [ -d "$_dr_probe" ] && [ ! -w "$_dr_probe" ]; }; then sed -i "s|^${key}=.*|${key}=${val}|" .env
if [ "$(id -u)" -ne 0 ]; then else
log "Filesystem setup under $_dr_probe needs sudo - authenticating now." printf '%s=%s\n' "$key" "$val" >> .env
sudo -v || die "sudo authentication failed"
fi
fi fi
}
# --- 1. host type: auto-detect, then override ------------------------------- # --- 1. host type: auto-detect, then override -------------------------------
step 1 6 "Host type"
detected="prod" detected="prod"
if grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then detected="wsl"; fi if grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null; then detected="wsl"; fi
if [ -n "$ARG_HOSTTYPE" ]; then if [ -n "$ARG_HOSTTYPE" ]; then
@ -443,8 +309,6 @@ if [ -n "$ARG_HOSTTYPE" ]; then
log "Host type: $HOSTTYPE (from flag; auto-detect said '$detected')" log "Host type: $HOSTTYPE (from flag; auto-detect said '$detected')"
else else
log "Auto-detected host type: $detected" log "Auto-detected host type: $detected"
echo " wsl = this distro runs under WSL2 (routers reach the stack via the Windows portproxy)"
echo " prod = native Linux host (routers reach the stack directly)"
ask HOSTTYPE "Host type (wsl/prod)" "$detected" ask HOSTTYPE "Host type (wsl/prod)" "$detected"
fi fi
case "$HOSTTYPE" in wsl|prod) ;; *) die "host type must be 'wsl' or 'prod' (got '$HOSTTYPE')";; esac case "$HOSTTYPE" in wsl|prod) ;; *) die "host type must be 'wsl' or 'prod' (got '$HOSTTYPE')";; esac
@ -491,7 +355,6 @@ normalize_type() {
*) echo "" ;; *) echo "" ;;
esac esac
} }
step 2 6 "Deployment type"
if [ -n "$ARG_SCOPE" ]; then if [ -n "$ARG_SCOPE" ]; then
DTYPE="$(normalize_type "$ARG_SCOPE")" DTYPE="$(normalize_type "$ARG_SCOPE")"
[ -n "$DTYPE" ] || die "unknown --scope '$ARG_SCOPE' (use full-stack|remote|central-store)" [ -n "$DTYPE" ] || die "unknown --scope '$ARG_SCOPE' (use full-stack|remote|central-store)"
@ -508,12 +371,6 @@ log "Deployment type: $DTYPE"
# --- 3. HOST_IP: smart default, editable, validated ------------------------- # --- 3. HOST_IP: smart default, editable, validated -------------------------
# HOST_IP is the address the INTERNAL stack advertises/binds (Kafka listener, # HOST_IP is the address the INTERNAL stack advertises/binds (Kafka listener,
# gobgp->collector). On a remote collector it is also this node's own address. # gobgp->collector). On a remote collector it is also this node's own address.
step 3 6 "Addresses (internal bind + what routers target)"
echo " Two different addresses are about to be asked for:"
echo " HOST_IP = where the stack binds INTERNALLY (Kafka, gobgp). On WSL"
echo " this is the WSL VM address."
echo " Router-facing = what routers put in 'bmp server host ...'. On WSL this"
echo " is the WINDOWS LAN IP (portproxy forwards it inward)."
primary_ip="$(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1 || true)" primary_ip="$(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1 || true)"
wsl_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)" wsl_ip="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"
current_ip="$(get_env HOST_IP)" current_ip="$(get_env HOST_IP)"
@ -604,13 +461,6 @@ BMP_STD_PORT=1790
default_router_port="${ARG_ROUTER_PORT:-$BMP_STD_PORT}" default_router_port="${ARG_ROUTER_PORT:-$BMP_STD_PORT}"
if [ -n "$ARG_ROUTER_IP" ]; then ROUTER_IP="$ARG_ROUTER_IP"; else if [ -n "$ARG_ROUTER_IP" ]; then ROUTER_IP="$ARG_ROUTER_IP"; else
ask ROUTER_IP "Router-facing IP (what 'bmp server' on the routers targets)" "$default_router_ip" ask ROUTER_IP "Router-facing IP (what 'bmp server' on the routers targets)" "$default_router_ip"
# A failed Windows-LAN-IP autodetect leaves a <WINDOWS_LAN_IP> placeholder
# as the default; don't let a non-address sail into .env silently.
while ! is_ipv4 "$ROUTER_IP"; do
[ "$ARG_YES" -eq 1 ] && die "router-facing IP '$ROUTER_IP' is not an IPv4 address (autodetect failed?) - pass --router-ip"
warn "'$ROUTER_IP' is not an IPv4 address."
ask ROUTER_IP "Router-facing IP (dotted quad)" ""
done
fi fi
if [ -n "$ARG_ROUTER_PORT" ]; then ROUTER_PORT="$ARG_ROUTER_PORT"; else if [ -n "$ARG_ROUTER_PORT" ]; then ROUTER_PORT="$ARG_ROUTER_PORT"; else
ask ROUTER_PORT "Router-facing BMP port" "$default_router_port" ask ROUTER_PORT "Router-facing BMP port" "$default_router_port"
@ -630,7 +480,6 @@ if [ "$DTYPE" = "remote" ]; then
fi fi
# --- 6. Authelia toggle (independent axis; N/A for remote - no local UI) ----- # --- 6. Authelia toggle (independent axis; N/A for remote - no local UI) -----
step 4 6 "Grafana authentication"
AUTH_MODE="local" AUTH_MODE="local"
OBMP_DOMAIN="$(get_env OBMP_DOMAIN)" OBMP_DOMAIN="$(get_env OBMP_DOMAIN)"
OBMP_COOKIE_DOMAIN="$(get_env OBMP_COOKIE_DOMAIN)" OBMP_COOKIE_DOMAIN="$(get_env OBMP_COOKIE_DOMAIN)"
@ -677,7 +526,6 @@ type_resources() {
central-store) echo "~16 vCPU / 48-64 GB / NVMe >=250 GB (carries all remotes' data)" ;; central-store) echo "~16 vCPU / 48-64 GB / NVMe >=250 GB (carries all remotes' data)" ;;
esac esac
} }
step 5 6 "Plan review"
echo; hr; log "Deployment plan"; hr echo; hr; log "Deployment plan"; hr
# Record what is being deployed - a greenfield deploy is only reproducible if # Record what is being deployed - a greenfield deploy is only reproducible if
# the checkout is pinned (branch + commit go in the plan and the terminal log). # the checkout is pinned (branch + commit go in the plan and the terminal log).
@ -720,11 +568,9 @@ else
fi fi
fi fi
# NOTE: default answer is No -- hitting Enter here ABORTS (safe default). if ! confirm "Proceed with this plan?"; then
if ! confirm "Proceed with this plan? (y = deploy, Enter/n = abort)"; then die "aborted before making changes - nothing was modified"
die "aborted before making changes - nothing was modified (answer 'y' to deploy)"
fi fi
step 6 6 "Provision + staged bring-up"
# --- 8. write .env ---------------------------------------------------------- # --- 8. write .env ----------------------------------------------------------
set_env HOST_IP "$HOST_IP" set_env HOST_IP "$HOST_IP"
@ -752,13 +598,10 @@ if [ "$ARG_RESET" -eq 1 ]; then
else else
warn "Resetting data tree under $OBMP_DATA_ROOT (WSL test host)" warn "Resetting data tree under $OBMP_DATA_ROOT (WSL test host)"
fi fi
docker compose "${ALL_PROFILES[@]}" down -v --remove-orphans 2>/dev/null || true docker compose --profile test --profile auth down -v 2>/dev/null || true
docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true docker volume rm obmp_data-volume obmp_ts-volume 2>/dev/null || true
# Wipe the tree but keep backups/ -- pg-backup.sh dumps live there and a sudo rm -rf "${OBMP_DATA_ROOT:?}/"* 2>/dev/null || true
# reset should not silently destroy them (use 'teardown --purge-backups' log "Reset complete"
# for that).
sudo find "${OBMP_DATA_ROOT:?}" -mindepth 1 -maxdepth 1 ! -name backups -exec rm -rf {} + 2>/dev/null || true
log "Reset complete (backups/ preserved if present)"
fi fi
# --- 10. run setup.sh ------------------------------------------------------- # --- 10. run setup.sh -------------------------------------------------------
@ -799,7 +642,7 @@ wait_kafka() {
done done
[ "$(docker inspect obmp-kafka --format '{{.State.Status}}' 2>/dev/null)" = running ] \ [ "$(docker inspect obmp-kafka --format '{{.State.Status}}' 2>/dev/null)" = running ] \
|| die "kafka did not stabilise - check: docker logs obmp-kafka" || die "kafka did not stabilise - check: docker logs obmp-kafka"
ok "kafka up" log " kafka up"
} }
wait_postgres() { wait_postgres() {
log " waiting for postgres ..." log " waiting for postgres ..."
@ -815,7 +658,7 @@ if [ "$DTYPE" = "remote" ]; then
wait_kafka wait_kafka
log "Stage 2/2 - collector (forwarding to $CENTRAL_KAFKA) ..." log "Stage 2/2 - collector (forwarding to $CENTRAL_KAFKA) ..."
docker compose up -d collector docker compose up -d collector
ok "collector up" log " collector up"
elif [ "$DTYPE" = "central-store" ]; then elif [ "$DTYPE" = "central-store" ]; then
# Store only: Postgres/psql-app/Grafana/feeders, NO local collector. # Store only: Postgres/psql-app/Grafana/feeders, NO local collector.
@ -825,12 +668,12 @@ elif [ "$DTYPE" = "central-store" ]; then
docker compose up -d psql docker compose up -d psql
wait_postgres wait_postgres
docker compose up -d psql-app grafana whois docker compose up -d psql-app grafana whois
ok "store up" log " store up"
log "Stage 3/3 - feeders/consumers (--profile test) ..." log "Stage 3/3 - feeders/consumers (--profile test) ..."
docker compose --profile test up -d docker compose --profile test up -d
# ensure no local collector is running on a central-store node # ensure no local collector is running on a central-store node
docker compose stop collector 2>/dev/null || true docker compose stop collector 2>/dev/null || true
ok "feeders up (no local collector)" log " feeders up (no local collector)"
else else
# full-stack: collector + store + feeders on one host. # full-stack: collector + store + feeders on one host.
@ -843,10 +686,10 @@ else
docker compose up -d psql docker compose up -d psql
wait_postgres wait_postgres
docker compose up -d collector psql-app grafana whois docker compose up -d collector psql-app grafana whois
ok "core up" log " core up"
log "Stage 3/3 - feeders${AUTH_MODE:+ + auth if authelia} (${profiles[*]}) ..." log "Stage 3/3 - feeders${AUTH_MODE:+ + auth if authelia} (${profiles[*]}) ..."
docker compose "${profiles[@]}" up -d docker compose "${profiles[@]}" up -d
ok "feeders up" log " feeders up"
fi fi
# --- 12. verify ------------------------------------------------------------- # --- 12. verify -------------------------------------------------------------
@ -858,13 +701,6 @@ docker logs obmp-collector --tail 8 2>&1 | sed 's/^/ /' || true
# --- 13. post-deploy notes -------------------------------------------------- # --- 13. post-deploy notes --------------------------------------------------
echo echo
printf '\033[1;32m[deploy] done\033[0m\n' printf '\033[1;32m[deploy] done\033[0m\n'
hr
log "Verify (any time):"
echo " docker compose ps # every service Up / healthy"
echo " docker exec -i obmp-psql psql -U openbmp -d openbmp -c 'SELECT name, state FROM routers;'"
echo " (DB schema is created automatically by psql-app on its first run)"
log "Tear down later: ./deploy.sh teardown (add --wipe-data for a full wipe)"
hr
if [ "$DTYPE" = "remote" ]; then if [ "$DTYPE" = "remote" ]; then
cat <<EOF cat <<EOF
Remote collector active. BMP ingest -> $CENTRAL_KAFKA (central store). Remote collector active. BMP ingest -> $CENTRAL_KAFKA (central store).

View File

@ -64,4 +64,4 @@ Storage scales with prefixes: ~1 GB per peer with a full internet table
> All figures are engineering estimates to calibrate against your own > All figures are engineering estimates to calibrate against your own
> watermarking, not measured specs. Replace them with real numbers once a > watermarking, not measured specs. Replace them with real numbers once a
> prod-realistic node has been load-tested (see production-sizing.md). > prod-realistic node has been load-tested (see docs/production-sizing.md).

View File

@ -1,14 +1,5 @@
# OpenBMP + ExaBGP Route Injector — Full Documentation # OpenBMP + ExaBGP Route Injector — Full Documentation
> **LEGACY LAB WALKTHROUGH.** This guide was written against the original
> single-host dev lab and keeps its literal values (e.g. host `10.40.40.202`,
> NETCONF user `webui`) as *illustrations* — substitute your own `.env`
> values (`HOST_IP`, `ROUTER_FACING_IP`/`PORT`, `IOSXR_NETCONF_*`). For
> current deployments start instead at:
> **[deploy.sh + README quickstart](../README.md)** (operators) and
> **[router-integration.md](router-integration.md)** (network engineers).
> Where this file disagrees with those, they win.
## Table of Contents ## Table of Contents
1. [What Is This Project?](#1-what-is-this-project) 1. [What Is This Project?](#1-what-is-this-project)
@ -57,7 +48,7 @@ This is a **BGP Monitoring Platform (BMP) lab stack** deployed via Docker Compos
``` ```
IOS-XR Routers (9x, AS 65020) IOS-XR Routers (9x, AS 65020)
BMP telemetry on TCP 1790 (maps to collector :5000 in-container) BMP telemetry on TCP 5000
| |
v v
obmp-collector (openbmp/collector:2.2.3) obmp-collector (openbmp/collector:2.2.3)
@ -110,7 +101,7 @@ Traffic Generator (Phase 4):
|-----------|-------|---------|------| |-----------|-------|---------|------|
| obmp-zookeeper | confluentinc/cp-zookeeper:7.1.1 | 2181 (internal) | Kafka coordination | | obmp-zookeeper | confluentinc/cp-zookeeper:7.1.1 | 2181 (internal) | Kafka coordination |
| obmp-kafka | confluentinc/cp-kafka:7.1.1 | 9092 | Message broker | | obmp-kafka | confluentinc/cp-kafka:7.1.1 | 9092 | Message broker |
| obmp-collector | openbmp/collector:2.2.3 | 1790 -> 5000 | BMP receiver (external ${ROUTER_FACING_PORT:-1790} maps to in-container 5000) | | obmp-collector | openbmp/collector:2.2.3 | 5000 | BMP receiver |
| obmp-psql-app | openbmp/psql-app:2.2.2 | 9005 | Kafka→PostgreSQL consumer | | obmp-psql-app | openbmp/psql-app:2.2.2 | 9005 | Kafka→PostgreSQL consumer |
| obmp-psql | openbmp/postgres:2.2.1 | 5432 | TimescaleDB storage | | obmp-psql | openbmp/postgres:2.2.1 | 5432 | TimescaleDB storage |
| obmp-grafana | grafana/grafana:9.1.7 | 3000 | Visualization | | obmp-grafana | grafana/grafana:9.1.7 | 3000 | Visualization |
@ -128,7 +119,7 @@ Traffic Generator (Phase 4):
- Docker Engine (20.10+) and Docker Compose v2 - Docker Engine (20.10+) and Docker Compose v2
- Host IP `10.40.40.202` reachable from the CML management network - Host IP `10.40.40.202` reachable from the CML management network
- CML routers with BMP configured pointing to the collector's router-facing address (see router-bmp-config.md) - CML routers with BMP configured pointing to the collector's router-facing address (see docs/router-bmp-config.md)
- CML CORE routers configured with ExaBGP as eBGP neighbor (see Section 5) - CML CORE routers configured with ExaBGP as eBGP neighbor (see Section 5)
- `OBMP_DATA_ROOT` directory created (default: `/var/openbmp`) - `OBMP_DATA_ROOT` directory created (default: `/var/openbmp`)
@ -190,15 +181,15 @@ mkdir -p ${OBMP_DATA_ROOT}/grafana/dashboards
sudo chmod -R 777 $OBMP_DATA_ROOT sudo chmod -R 777 $OBMP_DATA_ROOT
``` ```
### 4.3 Database initialisation (automatic) ### 4.3 Initialise the database (first run only)
psql-app creates the full schema **automatically on its first run** and then Create the init trigger file — this causes psql-app to create all tables on startup:
drops a marker file `${OBMP_DATA_ROOT}/config/do_not_init_db` so later
restarts skip it. No manual trigger is needed.
> To force a schema re-init against an empty database, delete the marker: ```bash
> `rm ${OBMP_DATA_ROOT}/config/do_not_init_db` and restart `obmp-psql-app`. touch ${OBMP_DATA_ROOT}/config/init_db
> Never do this against a database whose data you want to keep. ```
> **Warning:** Do not create this file on subsequent runs unless you want to wipe and recreate the entire database.
### 4.4 Copy Grafana provisioning files ### 4.4 Copy Grafana provisioning files
@ -563,7 +554,7 @@ Default credentials: `admin` / `openbmp` (anonymous access also enabled)
> History dashboards require `ip_rib_log` and `stats_chg_*` table data. Run `inject.py churn` to populate these. > History dashboards require `ip_rib_log` and `stats_chg_*` table data. Run `inject.py churn` to populate these.
### OBMP-Learning Dashboards (folder: `OBMP-Reference`) ### OBMP-Learning Dashboards (folder: `OBMP-Learning`)
Six learning-focused dashboards in a separate folder, designed to teach BGP concepts using live lab data. Six learning-focused dashboards in a separate folder, designed to teach BGP concepts using live lab data.
@ -578,7 +569,7 @@ Six learning-focused dashboards in a separate folder, designed to teach BGP conc
> **RPKI note:** The `rpki_validator` table is populated by a cron job in `psql-app` every 2 hours. Dashboard `obmp-learn-04` will show zero counts until the cron runs — check `ENABLE_RPKI=1` in `docker-compose.yml`. > **RPKI note:** The `rpki_validator` table is populated by a cron job in `psql-app` every 2 hours. Dashboard `obmp-learn-04` will show zero counts until the cron runs — check `ENABLE_RPKI=1` in `docker-compose.yml`.
### Advanced Analytics Dashboards (folder: `OBMP-Reference`) ### Advanced Analytics Dashboards (folder: `OBMP-Learning`)
Four advanced dashboards that go beyond basic BMP monitoring, unlocking TE/SR data and providing heuristic analysis. Four advanced dashboards that go beyond basic BMP monitoring, unlocking TE/SR data and providing heuristic analysis.
@ -593,7 +584,7 @@ Four advanced dashboards that go beyond basic BMP monitoring, unlocking TE/SR da
### Database Schema Reference ### Database Schema Reference
A standalone database schema reference is also available at `DB_SCHEMA.md`. It documents all 33 tables, 11 views, TE/SR columns, enum types, and common query patterns. A standalone database schema reference is also available at `docs/DB_SCHEMA.md`. It documents all 33 tables, 11 views, TE/SR columns, enum types, and common query patterns.
--- ---
@ -662,7 +653,7 @@ Should show topics like `openbmp.parsed.unicast_prefix`, `openbmp.parsed.peer`,
### 9.6 Grafana datasource ### 9.6 Grafana datasource
Open `http://${HOST_IP}:3000` → Configuration → Data Sources → PostgreSQL → Test. Open `http://10.40.40.202:3000` → Configuration → Data Sources → OpenBMP → Test.
Should return "Database Connection OK". Should return "Database Connection OK".
### 9.7 BMP collector receiving data ### 9.7 BMP collector receiving data
@ -831,7 +822,7 @@ Verify: `show bgp 1.1.1.0/24` — should show `Status: s (active), bestpath`.
### Grafana shows no data ### Grafana shows no data
1. Check datasource: Configuration → Data Sources → PostgreSQL → Test 1. Check datasource: Configuration → Data Sources → OpenBMP → Test
2. Verify psql-app is writing: `docker compose -p obmp logs psql-app` 2. Verify psql-app is writing: `docker compose -p obmp logs psql-app`
3. Check the database directly (see database queries above) 3. Check the database directly (see database queries above)
4. History dashboards need route churn — run `python3 inject.py churn` 4. History dashboards need route churn — run `python3 inject.py churn`
@ -890,7 +881,9 @@ Adjust in `docker-compose.yml` under the `psql-app` service environment block.
|----------|---------|-------------| |----------|---------|-------------|
| `EXABGP_LOCAL_IP` | `10.40.40.202` | Host IP ExaBGP binds to and uses as router-id | | `EXABGP_LOCAL_IP` | `10.40.40.202` | Host IP ExaBGP binds to and uses as router-id |
| `EXABGP_LOCAL_AS` | `65100` | ExaBGP's AS number | | `EXABGP_LOCAL_AS` | `65100` | ExaBGP's AS number |
| `EXABGP_PEERS` | (semicolon list) | Peers as `ip:peer_as:description;...` — replaces the retired `EXABGP_PEER_AS`/`EXABGP_PEER_1`/`EXABGP_PEER_2` | | `EXABGP_PEER_AS` | `65020` | AS of the IOS-XR lab |
| `EXABGP_PEER_1` | `10.100.0.100` | First CORE router to peer with |
| `EXABGP_PEER_2` | `10.100.0.200` | Second CORE router to peer with |
| `EXABGP_API_PORT` | `5050` | Flask API port | | `EXABGP_API_PORT` | `5050` | Flask API port |
### psql-app container (key variables) ### psql-app container (key variables)
@ -962,13 +955,12 @@ Expected: gRPC listening on port 57400.
### Telemetry Data Collected ### Telemetry Data Collected
Telegraf subscribes to two OpenConfig paths (see telegraf/telegraf.conf, Telegraf subscribes to two IOS-XR YANG paths at 10-second intervals:
the source of truth):
| Subscription | OpenConfig path | Interval | Data | | Subscription | YANG Path | Data |
|-------------|-----------------|----------|------| |-------------|-----------|------|
| interface_counters | `openconfig-interfaces:/interfaces/interface/state/counters` | 10s | bytes/packets in/out, errors, drops | | interface_counters | `Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/generic-counters` | bytes/packets in/out, errors, drops, CRC |
| interface_state | `openconfig-interfaces:/interfaces/interface/state` | 30s | oper status, MTU, speed | | interface_rates | `Cisco-IOS-XR-infra-statsd-oper:infra-statistics/interfaces/interface/latest/data-rate` | bits/sec in/out, packet rate |
### InfluxDB Access ### InfluxDB Access
@ -1118,6 +1110,6 @@ The **Combined BMP + Telemetry** dashboard shows both control-plane (BMP BGP upd
| Variable | Default | Description | | Variable | Default | Description |
|----------|---------|-------------| |----------|---------|-------------|
| `TRAFFIC_GEN_PORT` | `5051` | Flask API listen port | | `TRAFFIC_GEN_API_PORT` | `5051` | Flask API listen port |
| `TRAFFIC_GEN_MODE` | `sender` | Operating mode: `sender` or `responder` | | `TRAFFIC_GEN_MODE` | `sender` | Operating mode: `sender` or `responder` |
| `INFLUXDB_TOKEN` | `openbmp-telemetry-token` | InfluxDB auth token (Telegraf) | | `INFLUXDB_TOKEN` | `openbmp-telemetry-token` | InfluxDB auth token (Telegraf) |

View File

@ -41,7 +41,7 @@ feeders.
## 6. Size for BMP burst, not steady state ## 6. Size for BMP burst, not steady state
Generic docs say the collector is light — true, and misleading: the store Generic docs say the collector is light — true, and misleading: the store
saturates under burst (full table x monitored sessions, Postgres write saturates under burst (full table x monitored sessions, Postgres write
amplification). See DEPLOYMENT-TYPES.md for the full model. amplification). See docs/DEPLOYMENT-TYPES.md for the full model.
## 7. Ports 5000 and 3000 are crowded ## 7. Ports 5000 and 3000 are crowded
The collector's 5000 and Grafana's 3000 are commonly already occupied. The collector's 5000 and Grafana's 3000 are commonly already occupied.
@ -100,4 +100,4 @@ setup.sh renders the same port into gobgp's BMP export (gobgp is
host-networked and must hit the *published* port), and the cml tooling reads host-networked and must hit the *published* port), and the cml tooling reads
`ROUTER_FACING_IP`/`ROUTER_FACING_PORT` from `.env`. Remaining hands-on step: `ROUTER_FACING_IP`/`ROUTER_FACING_PORT` from `.env`. Remaining hands-on step:
re-apply the config to routers still pointed at 5000. See re-apply the config to routers still pointed at 5000. See
router-bmp-config.md. docs/router-bmp-config.md.

View File

@ -1,32 +0,0 @@
# Documentation index
Two tracks depending on who you are. Docs higher in each list are the entry
points; the rest are references you'll be routed to when needed.
## Operators — deploying and running the stack
| Doc | What it covers |
|---|---|
| [../README.md](../README.md) | Quickstart: clone, pin the checkout, run `deploy.sh` |
| [DEPLOYMENT-TYPES.md](DEPLOYMENT-TYPES.md) | The three `--scope` types (full-stack / remote / central-store) + burst sizing model |
| [PORTABILITY-FINDINGS.md](PORTABILITY-FINDINGS.md) | Every greenfield trap and how the tooling handles it — read before deploying to a new host |
| [production-sizing.md](production-sizing.md) | Memory limits and host sizing for production |
| [backup-restore.md](backup-restore.md) | Postgres backup/restore (`scripts/pg-backup.sh`) |
| [security-hardening.md](security-hardening.md) | Hardening checklist for exposed deployments |
| [DB_SCHEMA.md](DB_SCHEMA.md) | Full database schema reference (33 tables, views, query patterns) |
| [ROADMAP.md](ROADMAP.md) | Longer-term work items |
## Network engineers — feeding routers into the stack
| Doc | What it covers |
|---|---|
| [router-integration.md](router-integration.md) | **Start here.** The four ingest paths (BMP, BGP-LS, gNMI, NETCONF), bring-up order, verification |
| [router-bmp-config.md](router-bmp-config.md) | BMP deep-dive: address/port selection (WSL vs native), activation scope, troubleshooting |
| [../router-blueprints/](../router-blueprints/) | Copy-paste IOS-XR fragments with `<PLACEHOLDER>` substitutions |
## Legacy
| Doc | Status |
|---|---|
| [DOCS.md](DOCS.md) | Original single-host dev-lab walkthrough. Values are illustrative; where it disagrees with the docs above, they win. |
| [handoff/](handoff/) | Point-in-time session handoff packets, kept for the record. |

View File

@ -96,10 +96,9 @@ Replace hardcoded IPs in `docker-compose.yml` (Kafka listener, ExaBGP env vars).
Replace hardcoded gNMI addresses in `telegraf/telegraf.conf` with env var substitution. Pass `GNMI_TARGETS` from docker-compose.yml. Replace hardcoded gNMI addresses in `telegraf/telegraf.conf` with env var substitution. Pass `GNMI_TARGETS` from docker-compose.yml.
### A6. Fix InfluxDB datasource URL — DONE ### A6. Fix InfluxDB datasource URL
Completed: `obmp-grafana/provisioning/datasources/influxdb-ds.yml` now reads `obmp-grafana/provisioning/datasources/influxdb-ds.yml`: replace `http://10.40.40.202:8086` with `http://obmp-influxdb:8086`.
`http://obmp-influxdb:8086`.
--- ---

View File

@ -115,9 +115,8 @@ EOSQL
``` ```
> Restoring into a **brand-new container**? Bring `obmp-psql` up first and let > Restoring into a **brand-new container**? Bring `obmp-psql` up first and let
> it initialize, and create the skip-marker BEFORE psql-app's first start — > it initialize, but **do not** create the `config/init_db` trigger file —
> `touch ${OBMP_DATA_ROOT}/config/do_not_init_db` — so psql-app does not run > the schema comes from the dump, not from psql-app's first-run migration.
> its first-run schema migration; the schema comes from the dump instead.
### 3. Restore the dump ### 3. Restore the dump

View File

@ -44,7 +44,7 @@ bmp server 1 initial-refresh delay 60 spread 30
The `initial-refresh delay/spread` staggering matters at scale: it spreads The `initial-refresh delay/spread` staggering matters at scale: it spreads
the full-RIB dumps out when many routers (re)connect at once, which is the the full-RIB dumps out when many routers (re)connect at once, which is the
burst that sizes the whole store tier (DEPLOYMENT-TYPES.md). burst that sizes the whole store tier (docs/DEPLOYMENT-TYPES.md).
## Activating monitoring on BGP sessions ## Activating monitoring on BGP sessions
@ -52,7 +52,7 @@ BMP only reports sessions that are `bmp-activate`d:
``` ```
router bgp <ASN> router bgp <ASN>
neighbor-group RR-CLIENT neighbor-group RR-CLIENTS
bmp-activate server 1 bmp-activate server 1
! !
! !

View File

@ -81,10 +81,9 @@ it before configuring. Covers:
`HOST_IP`** on WSL deployments — the WSL VM's NAT address is `HOST_IP`** on WSL deployments — the WSL VM's NAT address is
unreachable from real routers; see unreachable from real routers; see
[PORTABILITY-FINDINGS.md](PORTABILITY-FINDINGS.md) finding 4). [PORTABILITY-FINDINGS.md](PORTABILITY-FINDINGS.md) finding 4).
- Which port (`ROUTER_FACING_PORT` — 1790, the IANA BMP port, standardized - Which port (`ROUTER_FACING_PORT`, defaults to 1790 — the IANA BMP port;
2026-07; the legacy 5000 is retired. Routers still configured for 5000 existing lab routers configured for 5000 need `--router-port 5000`
must be re-applied — `cml/proxmox_bmp_config.py` pushes the current until reconfigured).
`.env` values).
- The BMP `bmp server 1` block, flat formal form. - The BMP `bmp server 1` block, flat formal form.
- Activation via `neighbor-group BMP-MONITORED`. - Activation via `neighbor-group BMP-MONITORED`.
- **RR-scope-only activation** for load reduction — the single biggest - **RR-scope-only activation** for load reduction — the single biggest

View File

@ -5,7 +5,7 @@ present for the OpenBMP Docker stack to ingest data from it. Copy, edit
the `<PLACEHOLDER>` values for your topology, paste. the `<PLACEHOLDER>` values for your topology, paste.
For the walkthrough (what each ingest path does, in what order to apply, For the walkthrough (what each ingest path does, in what order to apply,
how to verify), see [docs/router-integration.md](../docs/router-integration.md). how to verify), see [docs/router-config-guide.md](../docs/router-config-guide.md).
## Layout ## Layout

View File

@ -62,5 +62,4 @@ router bgp <LOCAL_ASN>
! !
! On the stack side: ! On the stack side:
! docker logs obmp-collector 2>&1 | grep -i "peer up" ! docker logs obmp-collector 2>&1 | grep -i "peer up"
! Grafana -> OBMP-Operations -> Inventory (router appears, connected) ! Grafana -> OBMP-Operations -> Base-1001 -> Peers Table
! Grafana -> OBMP-Operations -> Peer Detail (per-session state + prefixes)

View File

@ -32,21 +32,6 @@ router bgp <LOCAL_ASN>
bmp-activate server 1 bmp-activate server 1
address-family ipv4 unicast address-family ipv4 unicast
route-reflector-client route-reflector-client
!
! *** NEXT-HOP-SELF CAVEAT *********************************************
! next-hop-self here ONLY rewrites the next-hop of eBGP-learned and
! locally-originated routes advertised to clients. That is exactly what
! this lab wants: the full table enters at the cores via eBGP (GoBGP/
! ExaBGP) and clients must resolve those routes via the RR's loopback.
!
! IOS-XR will NOT rewrite the next-hop of iBGP-REFLECTED routes unless
! you ALSO configure:
! router bgp <LOCAL_ASN>
! ibgp policy out enforce-modifications
! On a pure route reflector with no eBGP feed, this line is a silent
! no-op and clients must resolve next-hops via the IGP instead. Do not
! copy it into such a design expecting reflected-route NH rewrite.
! **********************************************************************
next-hop-self next-hop-self
! !
address-family link-state link-state address-family link-state link-state
@ -69,4 +54,4 @@ router bgp <LOCAL_ASN>
! show bgp neighbors <CLIENT_LOOPBACK> advertised-routes | count ! show bgp neighbors <CLIENT_LOOPBACK> advertised-routes | count
! !
! On the stack side (once the routers are BMP-reporting and reflecting): ! On the stack side (once the routers are BMP-reporting and reflecting):
! Grafana -> OBMP-Reference -> RR Loc-RIB Diff (should populate) ! Grafana -> OBMP-Learning -> RR Loc-RIB Diff (should populate)

View File

@ -1,90 +0,0 @@
#!/usr/bin/env bash
#
# deploy-lib.sh - shared helpers for deploy.sh and setup.sh.
#
# Both scripts source this file so output styling and .env handling stay
# identical and are defined exactly once. Safe to source multiple times.
#
# Provides:
# log / ok / warn / die / hr - consistent colored output
# banner "title" - boxed section banner
# step N M "title" - numbered decision/stage header
# ask VAR "prompt" "default" - editable-default prompt (honors ARG_YES)
# confirm "prompt" - y/N confirm (honors ARG_YES)
# get_env KEY / set_env KEY VAL - .env read/write without sourcing it
# is_ipv4 ADDR - dotted-quad shape check
#
# Conventions: callers may set ARG_YES=1 for unattended runs and TAG to
# change the log prefix (defaults to "deploy").
TAG="${TAG:-deploy}"
ARG_YES="${ARG_YES:-0}"
log() { printf '\033[1;36m[%s]\033[0m %s\n' "$TAG" "$*"; }
ok() { printf '\033[1;32m[%s] \xe2\x9c\x94\033[0m %s\n' "$TAG" "$*"; }
warn() { printf '\033[1;33m[%s][warn]\033[0m %s\n' "$TAG" "$*" >&2; }
die() { printf '\033[1;31m[%s][err]\033[0m %s\n' "$TAG" "$*" >&2; exit 1; }
hr() { printf '\033[2m%s\033[0m\n' "----------------------------------------------------------------"; }
# Boxed banner, e.g.:
# +--------------------------------------------------------------+
# | OpenBMP stack deploy |
# +--------------------------------------------------------------+
banner() {
local title="$1" width=62
printf '\033[1;36m+%s+\n' "$(printf -- '-%.0s' $(seq 1 $width))"
printf '| %-*s|\n' "$((width - 2))" "$title"
printf '+%s+\033[0m\n' "$(printf -- '-%.0s' $(seq 1 $width))"
}
# Numbered header for decisions and stages: step 2 5 "Deployment type"
step() {
local n="$1" total="$2" title="$3"
printf '\n\033[1;36m[%s] Step %s/%s\033[0m \033[1m%s\033[0m\n' "$TAG" "$n" "$total" "$title"
}
# Prompt with an editable default. Honors ARG_YES (accepts default silently).
ask() {
local __var="$1" __prompt="$2" __default="${3:-}" __reply=""
if [ "$ARG_YES" -eq 1 ]; then
printf -v "$__var" '%s' "$__default"; return
fi
if [ -n "$__default" ]; then
read -r -p "$__prompt [$__default]: " __reply
__reply="${__reply:-$__default}"
else
read -r -p "$__prompt: " __reply
fi
printf -v "$__var" '%s' "$__reply"
}
confirm() {
local __prompt="$1" __reply=""
[ "$ARG_YES" -eq 1 ] && return 0
read -r -p "$__prompt [y/N]: " __reply
[ "$__reply" = "y" ] || [ "$__reply" = "Y" ]
}
# Read a single KEY=value from .env without sourcing it -- .env contains keys
# with hyphens (PROX-CML_*) that a shell `source` would choke on.
get_env() { grep -E "^$1=" .env | head -1 | cut -d= -f2- || true; }
# Set KEY=value in .env: replace the line if present, else append.
set_env() {
local key="$1" val="$2"
if grep -qE "^${key}=" .env; then
# `|` delimiter -- values are hex/IPs, no `|`.
sed -i "s|^${key}=.*|${key}=${val}|" .env
else
printf '%s=%s\n' "$key" "$val" >> .env
fi
}
# Dotted-quad shape check (rejects placeholders like <WINDOWS_LAN_IP>).
is_ipv4() {
local ip="$1" o
[[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || return 1
IFS=. read -r -a o <<<"$ip"
for x in "${o[@]}"; do [ "$x" -le 255 ] || return 1; done
return 0
}

View File

@ -20,19 +20,14 @@
.PARAMETER WslIp .PARAMETER WslIp
WSL address to forward to. Default: auto-detected via 'wsl hostname -I'. WSL address to forward to. Default: auto-detected via 'wsl hostname -I'.
.PARAMETER Remove
Teardown mode: delete the portproxy + firewall rules this script creates
(pair with './deploy.sh teardown' on the Linux side).
.EXAMPLE .EXAMPLE
powershell -ExecutionPolicy Bypass -File scripts\wsl-portproxy.ps1 powershell -ExecutionPolicy Bypass -File scripts\wsl-portproxy.ps1
powershell -ExecutionPolicy Bypass -File scripts\wsl-portproxy.ps1 -Remove powershell -ExecutionPolicy Bypass -File scripts\wsl-portproxy.ps1 -RouterPort 5000
#> #>
param( param(
[int]$RouterPort = 1790, [int]$RouterPort = 1790,
[int]$KafkaPort = 9092, [int]$KafkaPort = 9092,
[string]$WslIp = "", [string]$WslIp = ""
[switch]$Remove
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@ -44,24 +39,6 @@ if (-not (New-Object System.Security.Principal.WindowsPrincipal($id)).IsInRole(
exit 1 exit 1
} }
# -Remove: teardown mode. Deletes the portproxy + firewall rules this script
# creates (used by './deploy.sh teardown' guidance). No WSL IP needed.
if ($Remove) {
foreach ($listen in @($RouterPort, $KafkaPort)) {
netsh interface portproxy delete v4tov4 listenport=$listen listenaddress=0.0.0.0 2>$null | Out-Null
Write-Host "portproxy rule removed: 0.0.0.0:$listen"
$ruleName = "OpenBMP $listen"
if (Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue) {
Remove-NetFirewallRule -DisplayName $ruleName
Write-Host "firewall rule removed: $ruleName"
}
}
Write-Host ""
Write-Host "Remaining portproxy rules:"
netsh interface portproxy show v4tov4
exit 0
}
if (-not $WslIp) { if (-not $WslIp) {
$WslIp = (wsl hostname -I).Trim().Split(" ")[0] $WslIp = (wsl hostname -I).Trim().Split(" ")[0]
if (-not $WslIp) { Write-Error "Could not auto-detect the WSL IP; pass -WslIp."; exit 1 } if (-not $WslIp) { Write-Error "Could not auto-detect the WSL IP; pass -WslIp."; exit 1 }

View File

@ -27,10 +27,20 @@ if [ ! -f .env ]; then
exit 1 exit 1
fi fi
# Shared helpers (log ok warn die hr get_env set_env ...) — same styling as # Read a single KEY=value from .env without sourcing it — .env contains keys
# deploy.sh, defined once in scripts/deploy-lib.sh. # with hyphens (PROX-CML_*) that a shell `source` would choke on.
TAG=setup get_env() { grep -E "^$1=" .env | head -1 | cut -d= -f2- || true; }
. "$(dirname "$0")/scripts/deploy-lib.sh"
# Set KEY=value in .env: replace the line if present, else append.
set_env() {
local key="$1" val="$2"
if grep -qE "^${key}=" .env; then
# `|` delimiter — values are hex, no `|`.
sed -i "s|^${key}=.*|${key}=${val}|" .env
else
printf '%s=%s\n' "$key" "$val" >> .env
fi
}
OBMP_DATA_ROOT="$(get_env OBMP_DATA_ROOT)" OBMP_DATA_ROOT="$(get_env OBMP_DATA_ROOT)"
OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}" OBMP_DATA_ROOT="${OBMP_DATA_ROOT:-/var/openbmp}"