Commit 10b9f1a5 authored by Yann Garcia's avatar Yann Garcia
Browse files

Adding basic tools for Simu5G skeletons

parent aae5c7fc
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
"""Dummy Simu5G bridge echo endpoint.

Accepts framed packets and reflects DATA_UL as DATA_DL.
Useful for local bridge MVP tests before OMNeT++ module exists.
"""

import argparse
import socket
import struct

MAGIC = 0x5335
TYPE_DATA_UL = 0x01
TYPE_DATA_DL = 0x02


def recv_exact(conn, size):
    buf = b""
    while len(buf) < size:
        chunk = conn.recv(size - len(buf))
        if not chunk:
            return None
        buf += chunk
    return buf


def run(host: str, port: int):
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    srv.bind((host, port))
    srv.listen(1)
    print(f"dummy echo listening on {host}:{port}")
    conn, addr = srv.accept()
    print(f"client connected: {addr}")
    with conn:
        while True:
            hdr = recv_exact(conn, 8)
            if hdr is None:
                break
            magic, ver, ftype, length = struct.unpack(">HBBI", hdr)
            if magic != MAGIC:
                print("invalid magic")
                break
            payload = recv_exact(conn, length)
            if payload is None:
                break
            if ftype != TYPE_DATA_UL:
                continue
            # turn UL into DL and send same payload back
            out_hdr = struct.pack(">HBBI", MAGIC, ver, TYPE_DATA_DL, len(payload))
            conn.sendall(out_hdr + payload)


if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--host", default="127.0.0.1")
    p.add_argument("--port", type=int, default=5555)
    args = p.parse_args()
    run(args.host, args.port)
+54 −0
Original line number Diff line number Diff line
{
  "deployment": {
    "scenarioId": "4g-5g-macro-v2x-fed",
    "sandboxHost": "10.0.1.10",
    "simHost": "172.29.10.20"
  },
  "bridge": {
    "listenAddress": "0.0.0.0",
    "listenPort": 5555,
    "connectAddress": "172.29.10.20",
    "connectPort": 5555,
    "tunName": "simu5g-br0"
  },
  "telemetry": {
    "listenAddress": "0.0.0.0",
    "listenPort": 9110,
    "sceneLatDeg": 43.734042,
    "sceneLonDeg": 7.416587,
    "sceneAltM": 0,
    "metersPerDeg": 111320,
    "jsonOut": true
  },
  "rni": {
    "simPort": 9201,
    "pollIntervalMs": 1000,
    "meAppInst": "4g-5g-macro-v2x-fed"
  },
  "ueMappings": [
    {
      "ueId": "ue1",
      "sandboxIpCidr": "10.10.1.0/24",
      "simu5gModulePath": "ue_car[0]"
    }
  ],
  "mecEndpoints": [
    {
      "endpointId": "mec1",
      "sandboxIpCidr": "10.20.0.0/24",
      "simu5gModulePath": "router"
    }
  ],
  "cells": [
    {
      "poaId": "5g-small-cell-1",
      "cellId": 512,
      "simu5gModulePath": "gnb"
    }
  ],
  "options": {
    "mtu": 1400,
    "maxPendingPackets": 10000,
    "realTimeToleranceMs": 50
  }
}
+65 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
"""Generate Simu5G/SUMO scaffold artifacts from sandbox scenario YAML."""

import argparse
import json
from pathlib import Path

import yaml


def load_yaml(path: Path):
    with path.open("r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def collect_counts(scenario: dict):
    text = json.dumps(scenario)
    return {
        "poa4g": text.count("poa4GConfig"),
        "poa5g": text.count("poa5GConfig"),
        "ue": text.count("\"type\": \"UE\"") + text.count("\"type\":\"UE\""),
    }


def render_ini(counts: dict) -> str:
    return f"""[General]
network = simu5g.scenarios.fourg5gmacrov2xfed.Simu5g4g5gMacroV2xFed
sim-time-limit = 100s
**.enb[*].cellId = 0x100 + index
**.gnb[*].cellId = 0x200 + index
# Derived hints:
# 4G cells: {counts['poa4g']}
# 5G cells: {counts['poa5g']}
# UEs: {counts['ue']}
"""


def render_routes_xml() -> str:
    return """<routes>
  <!-- TODO: generate SUMO routes from scenario UE paths -->
</routes>
"""


def main():
    p = argparse.ArgumentParser()
    p.add_argument("--scenario", required=True, help="Path to sandbox scenario YAML")
    p.add_argument("--out-dir", required=True, help="Output directory")
    args = p.parse_args()

    scenario_path = Path(args.scenario)
    out_dir = Path(args.out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)

    scenario = load_yaml(scenario_path)
    counts = collect_counts(scenario)

    (out_dir / "generated-simu5g.ini").write_text(render_ini(counts), encoding="utf-8")
    (out_dir / "generated-routes.rou.xml").write_text(render_routes_xml(), encoding="utf-8")
    (out_dir / "generation-summary.json").write_text(json.dumps(counts, indent=2), encoding="utf-8")
    print(f"Generated artifacts in {out_dir}")


if __name__ == "__main__":
    main()
+317 −0
Original line number Diff line number Diff line
#!/usr/bin/env bash
# install-simu5g-routes-sandbox.sh — Discover UE/MEC PodIPs from a sandbox namespace
# by pod name, then delegate to install-simu5g-routes.sh (unchanged).
#
# Classification (among scenario pods):
#   MEC  — pod name starts with MEC_NAME_PREFIX (default: mep)
#   UE   — all other scenario pods with a PodIP
#
# The derived selectors are informational (kubectl -l cannot express name prefixes);
# this script resolves PodIPs and passes UE_CIDRS / MEC_CIDRS to the base installer.
#
# Usage:
#   ./install-simu5g-routes-sandbox.sh discover -n sbxvipkywt
#   sudo ./install-simu5g-routes-sandbox.sh setup-tun -n sbxvipkywt
#   sudo ./install-simu5g-routes-sandbox.sh apply -n sbxvipkywt
#   sudo ./install-simu5g-routes-sandbox.sh status
#   sudo ./install-simu5g-routes-sandbox.sh delete-tun
#   sudo ./install-simu5g-routes-sandbox.sh rollback

set -euo pipefail

readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly INSTALLER="${SCRIPT_DIR}/install-simu5g-routes.sh"
readonly SCRIPT_NAME="${0##*/}"

KUBECTL="${KUBECTL:-kubectl}"
SANDBOX_NS="${SANDBOX_NS:-sbxvipkywt}"
SCENARIO_SELECTOR="${SCENARIO_SELECTOR:-meepOrigin=scenario}"
MEC_NAME_PREFIX="${MEC_NAME_PREFIX:-mep}"
CONFIG_FILE="${CONFIG_FILE:-${SCRIPT_DIR}/example-integration-config.json}"

# Host routing / TUN (same defaults as install-simu5g-routes.sh)
TUN_DEV="${TUN_DEV:-simu5g-br0}"
TUN_ADDR="${TUN_ADDR:-169.254.250.1/32}"
CNI_DEV="${CNI_DEV:-weave}"
POD_CIDR="${POD_CIDR:-10.32.0.0/12}"
MGMT_DEV="${MGMT_DEV:-ens160}"
UPLINK_ONLY="${UPLINK_ONLY:-0}"
STATE_DIR="${STATE_DIR:-/var/lib/simu5g-routes}"

# Populated by discover_pod_groups (exported for install-simu5g-routes.sh)
UE_POD_SELECTOR=""
MEC_POD_SELECTOR=""
UE_CIDRS=""
MEC_CIDRS=""

log() { printf '[%s] %s\n' "$SCRIPT_NAME" "$*"; }
die() { log "ERROR: $*"; exit 1; }

require_root() {
  [[ "${EUID:-$(id -u)}" -eq 0 ]] || die "run as root (sudo $SCRIPT_NAME $*)"
}

# Create and bring up the bridge TUN (mirrors install-simu5g-routes.sh setup_tun).
setup_tun() {
  if ! ip link show "$TUN_DEV" >/dev/null 2>&1; then
    ip tuntap add dev "$TUN_DEV" mode tun user root
    log "created TUN $TUN_DEV"
  else
    log "TUN $TUN_DEV already exists"
  fi
  ip addr add "$TUN_ADDR" dev "$TUN_DEV" 2>/dev/null || true
  ip link set "$TUN_DEV" up
  log "TUN $TUN_DEV up with $TUN_ADDR"
}

show_tun_status() {
  if ip link show "$TUN_DEV" >/dev/null 2>&1; then
    ip -br addr show dev "$TUN_DEV" 2>/dev/null | sed 's/^/  /' || true
  else
    printf '  (not created — run: sudo %s setup-tun or apply)\n' "$SCRIPT_NAME"
  fi
}

usage() {
  cat <<EOF
Usage: $SCRIPT_NAME {discover|setup-tun|apply|status|rollback|delete-tun} [options]

Discover scenario pods in a sandbox namespace and classify by pod name:
  MEC  — name starts with "${MEC_NAME_PREFIX}"  → MEC_POD_SELECTOR (logical)
  UE   — other scenario pods                   → UE_POD_SELECTOR (logical)

Options (environment or flags):
  -n, --namespace NS     Sandbox namespace (default: ${SANDBOX_NS})
  --scenario-selector S  kubectl -l filter before name split (default: ${SCENARIO_SELECTOR})
  --mec-prefix P         MEC pod name prefix (default: ${MEC_NAME_PREFIX})
  --config-file PATH     Passed to install-simu5g-routes.sh (default: example-integration-config.json)

TUN / host routing (passed through to ${INSTALLER##*/}):
  TUN_DEV=$TUN_DEV  TUN_ADDR=$TUN_ADDR  CNI_DEV=$CNI_DEV

Examples:
  $SCRIPT_NAME discover -n sbxvipkywt
  sudo $SCRIPT_NAME setup-tun
  sudo $SCRIPT_NAME apply -n sbxvipkywt
  sudo $SCRIPT_NAME status
  sudo $SCRIPT_NAME delete-tun
  sudo $SCRIPT_NAME rollback

Delegates routing install to: ${INSTALLER}
EOF
}

parse_args() {
  ACTION=""
  while [[ $# -gt 0 ]]; do
    case "$1" in
      discover|setup-tun|apply|status|rollback|delete-tun)
        [[ -z "$ACTION" ]] || die "duplicate action: $1"
        ACTION="$1"
        shift
        ;;
      -n|--namespace)
        SANDBOX_NS="${2:?namespace required}"
        shift 2
        ;;
      --scenario-selector)
        SCENARIO_SELECTOR="${2:?selector required}"
        shift 2
        ;;
      --mec-prefix)
        MEC_NAME_PREFIX="${2:?prefix required}"
        shift 2
        ;;
      --config-file)
        CONFIG_FILE="${2:?config path required}"
        shift 2
        ;;
      -h|--help|help)
        usage
        exit 0
        ;;
      *)
        die "unknown argument: $1 (use: $SCRIPT_NAME discover -n <namespace>)"
        ;;
    esac
  done
  [[ -n "$ACTION" ]] || { usage; exit 1; }
}

is_mec_pod_name() {
  local name=$1
  [[ "$name" == "${MEC_NAME_PREFIX}"* ]]
}

pods_to_cidr_csv() {
  local csv=""
  while read -r ip; do
    [[ -n "$ip" ]] || continue
    csv+="${ip}/32,"
  done
  printf '%s' "${csv%,}"
}

# List scenario pods: "name<TAB>ip" per line (portable jsonpath — no ?() filter).
list_scenario_pods_with_selector() {
  local selector=$1
  local out
  out="$("$KUBECTL" -n "$SANDBOX_NS" get pods -l "$selector" \
    -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}' 2>/dev/null)" || return 1
  while IFS=$'\t' read -r name ip; do
    [[ -n "$name" && -n "$ip" ]] || continue
    printf '%s\t%s\n' "$name" "$ip"
  done <<< "$out"
}

list_scenario_pods() {
  local tmp rows=0
  if ! "$KUBECTL" get namespace "$SANDBOX_NS" >/dev/null 2>&1; then
    die "namespace not found: $SANDBOX_NS (check kubectl context)"
  fi

  tmp="$(mktemp)"
  list_scenario_pods_with_selector "$SCENARIO_SELECTOR" >"$tmp" || true
  rows=$(grep -c . "$tmp" 2>/dev/null || echo 0)

  if [[ "$rows" -eq 0 ]]; then
    local fallback="meepSandbox=${SANDBOX_NS}"
    log "WARN: no pods for -l ${SCENARIO_SELECTOR}; trying fallback -l ${fallback}"
    list_scenario_pods_with_selector "$fallback" >"$tmp" || true
    rows=$(grep -c . "$tmp" 2>/dev/null || echo 0)
  fi

  cat "$tmp"
  rm -f "$tmp"
}

discover_pod_groups() {
  command -v "$KUBECTL" >/dev/null 2>&1 || die "kubectl not found"

  local mec_names=() ue_names=()
  local mec_ips=() ue_ips=()

  while IFS=$'\t' read -r name ip; do
    [[ -n "$name" && -n "$ip" ]] || continue
    if is_mec_pod_name "$name"; then
      mec_names+=("$name")
      mec_ips+=("$ip")
    else
      ue_names+=("$name")
      ue_ips+=("$ip")
    fi
  done < <(list_scenario_pods)

  # Logical selectors (document how pods were chosen; not valid as a single kubectl -l name filter).
  MEC_POD_SELECTOR="${SCENARIO_SELECTOR} + pod name ^${MEC_NAME_PREFIX}"
  UE_POD_SELECTOR="${SCENARIO_SELECTOR} + pod name !^${MEC_NAME_PREFIX}"

  MEC_CIDRS=$(printf '%s\n' "${mec_ips[@]:-}" | pods_to_cidr_csv)
  UE_CIDRS=$(printf '%s\n' "${ue_ips[@]:-}" | pods_to_cidr_csv)

  export MEC_POD_SELECTOR UE_POD_SELECTOR MEC_CIDRS UE_CIDRS

  printf '%s\n' "namespace=${SANDBOX_NS}"
  printf '%s\n' "scenario_selector=${SCENARIO_SELECTOR}"
  printf '%s\n' "MEC_POD_SELECTOR=${MEC_POD_SELECTOR}"
  printf '%s\n' "UE_POD_SELECTOR=${UE_POD_SELECTOR}"
  printf '%s\n' "MEC_CIDRS=${MEC_CIDRS:-<none>}"
  printf '%s\n' "UE_CIDRS=${UE_CIDRS:-<none>}"
  printf '%s\n' "TUN_DEV=${TUN_DEV} TUN_ADDR=${TUN_ADDR} (use setup-tun or apply to create)"
  printf '\n%s\n' "TUN interface:"
  show_tun_status
  printf '\n%s\n' "MEC pods (${#mec_names[@]}):"
  if [[ ${#mec_names[@]} -eq 0 ]]; then
    printf '  (none)\n'
  else
    local i
    for i in "${!mec_names[@]}"; do
      printf '  %s  %s/32\n' "${mec_names[$i]}" "${mec_ips[$i]}"
    done
  fi
  printf '\n%s\n' "UE pods (${#ue_names[@]}):"
  if [[ ${#ue_names[@]} -eq 0 ]]; then
    printf '  (none)\n'
  else
    local i
    for i in "${!ue_names[@]}"; do
      printf '  %s  %s/32\n' "${ue_names[$i]}" "${ue_ips[$i]}"
    done
  fi

  [[ -n "$MEC_CIDRS" ]] || die "no MEC pods (name prefix '${MEC_NAME_PREFIX}') in namespace ${SANDBOX_NS}"
  [[ -n "$UE_CIDRS" ]] || die "no UE pods (non-${MEC_NAME_PREFIX} scenario pods) in namespace ${SANDBOX_NS}"
}

run_installer() {
  local action=$1
  [[ -x "$INSTALLER" ]] || chmod +x "$INSTALLER"
  DISCOVER_PODS=0 \
    SANDBOX_NS="$SANDBOX_NS" \
    UE_CIDRS="$UE_CIDRS" \
    MEC_CIDRS="$MEC_CIDRS" \
    CONFIG_FILE="$CONFIG_FILE" \
    TUN_DEV="$TUN_DEV" \
    TUN_ADDR="$TUN_ADDR" \
    CNI_DEV="$CNI_DEV" \
    POD_CIDR="$POD_CIDR" \
    MGMT_DEV="$MGMT_DEV" \
    UPLINK_ONLY="$UPLINK_ONLY" \
    STATE_DIR="$STATE_DIR" \
    "$INSTALLER" "$action"
}

cmd_setup_tun() {
  require_root setup-tun
  setup_tun
  log "TUN ready for meep-simu5g-bridge (attach to $TUN_DEV)"
  show_tun_status
}

cmd_delete_tun() {
  require_root delete-tun
  ip link del "$TUN_DEV" 2>/dev/null || true
  log "TUN $TUN_DEV deleted"
}

main() {
  parse_args "$@"

  case "$ACTION" in
    discover)
      discover_pod_groups
      ;;
    setup-tun)
      cmd_setup_tun
      ;;
    delete-tun)
      cmd_delete_tun
      ;;
    apply)
      require_root apply
      discover_pod_groups >/dev/null
      log "MEC_POD_SELECTOR=${MEC_POD_SELECTOR}"
      log "UE_POD_SELECTOR=${UE_POD_SELECTOR}"
      log "MEC_CIDRS=${MEC_CIDRS}"
      log "UE_CIDRS=${UE_CIDRS}"
      setup_tun
      run_installer apply
      ;;
    status)
      run_installer status
      ;;
    rollback)
      require_root rollback
      run_installer rollback
      ;;
    -h|--help|help)
      usage
      ;;
    *)
      usage
      exit 1
      ;;
  esac
}

main "$@"
+340 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading