diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11eeda1..672b592 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [ main] + branches: [ main ] pull_request: branches: [ main ] diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 0000000..ffb8f0a --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,120 @@ +name: Integration Test + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + integration-test: + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Free up disk space + run: | + sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc + sudo apt-get clean + df -h + + - name: Build UE and gNB plugins + run: | + docker run --rm \ + -v "${{ github.workspace }}:/workspace" \ + osrf/ros:humble-desktop \ + /bin/bash -c ' + set -e + apt-get update && apt-get install -y \ + python3-colcon-common-extensions \ + wget lsb-release gnupg2 + + # Add Gazebo Ignition key and repo (Fortress) + echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" \ + > /etc/apt/sources.list.d/gazebo-stable.list + wget https://packages.osrfoundation.org/gazebo.key -O - | apt-key add - + apt-get update && apt-get install -y ignition-fortress + + cd /workspace/phine-plugins + source /opt/ros/humble/setup.bash + colcon build --event-handlers console_direct+ + + # Verify plugins were built + ls -la /workspace/phine-plugins/install/phine_plugins/lib/phine_plugins/ + ' + + - name: Copy built plugins to simulation image + run: | + PLUGIN_SRC=phine-plugins/install/phine_plugins/lib/phine_plugins + PLUGIN_DST=demo_containerized/images/simulation/gazebo_launch/src/ign_turtlebot/phine_plugins + + cp "${PLUGIN_SRC}/libgNB_plugin.so" "${PLUGIN_DST}/libgNB_plugin.so" + cp "${PLUGIN_SRC}/libUE_plugin.so" "${PLUGIN_DST}/libUE_plugin.so" + + echo "Plugins copied:" + ls -la "${PLUGIN_DST}/" + + - name: Build simulation Docker image + run: | + cd demo_containerized/images/simulation + docker build -t simulation:test . + + - name: Start OAI Core Network + run: | + cd demo_containerized/oai_setup + docker compose up -d + ./wait_for_core_network.sh + + - name: Create ros_gz_net network + run: | + docker network create \ + --driver bridge \ + --subnet=192.168.73.128/26 \ + --opt com.docker.network.bridge.name="ros_gz_net" \ + ros_gz_net + + - name: Start simulation container (headless) + run: | + cd tests/integration + DEMO_PROJECT_PATH="${{ github.workspace }}/demo_containerized" \ + docker compose -f docker-compose-test-simulation.yml up -d + + - name: Check gNB and UE registration + run: | + chmod +x tests/integration/check_registration.sh + tests/integration/check_registration.sh 900 + + - name: Collect logs on failure + if: failure() + run: | + echo "=== AMF logs ===" + docker logs oai-amf 2>&1 | tail -100 || true + echo "" + echo "=== Simulation container logs ===" + docker logs simulation 2>&1 | tail -100 || true + echo "" + echo "=== gNB container logs ===" + docker logs oai-gNB1 2>&1 | tail -50 || true + echo "" + echo "=== UE container logs ===" + docker logs ue_turtlebot 2>&1 | tail -50 || true + echo "" + echo "=== All running containers ===" + docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true + echo "" + echo "=== Docker networks ===" + docker network ls || true + + - name: Cleanup + if: always() + run: | + cd tests/integration + docker compose -f docker-compose-test-simulation.yml down || true + docker stop ue_turtlebot oai-gNB1 2>/dev/null || true + docker rm ue_turtlebot oai-gNB1 2>/dev/null || true + cd "${{ github.workspace }}/demo_containerized/oai_setup" + docker compose down || true + docker network rm ros_gz_net 2>/dev/null || true diff --git a/tests/integration/check_registration.sh b/tests/integration/check_registration.sh new file mode 100755 index 0000000..46079bc --- /dev/null +++ b/tests/integration/check_registration.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# Check OAI AMF logs for gNB and UE registration +# Usage: ./check_registration.sh [timeout_seconds] + +set -e + +TIMEOUT=${1:-600} +INTERVAL=15 +ELAPSED=0 + +GNB_CONTAINER_UP=false +UE_CONTAINER_UP=false +GNB_REGISTERED=false +UE_REGISTERED=false + +echo "============================================" +echo "Integration Test: gNB and UE Registration" +echo "Timeout: ${TIMEOUT}s" +echo "============================================" + +# Phase 1: Wait for gNB and UE containers to be started by the plugins +echo "" +echo "Phase 1: Waiting for gNB and UE containers to appear..." +echo "(The UE plugin builds a Docker image first, this can take several minutes)" +echo "" + +while [ $ELAPSED -lt $TIMEOUT ]; do + # Check if gNB container exists + if [ "$GNB_CONTAINER_UP" = false ]; then + if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "oai-gNB"; then + echo "[OK] gNB container is running (elapsed: ${ELAPSED}s)" + GNB_CONTAINER_UP=true + fi + fi + + # Check if UE container exists (ue_turtlebot from the SDF config) + if [ "$UE_CONTAINER_UP" = false ]; then + if docker ps --format '{{.Names}}' 2>/dev/null | grep -qE "(ue_turtlebot|robot_1)"; then + echo "[OK] UE container is running (elapsed: ${ELAPSED}s)" + UE_CONTAINER_UP=true + fi + fi + + # Both containers running, move to phase 2 + if [ "$GNB_CONTAINER_UP" = true ] && [ "$UE_CONTAINER_UP" = true ]; then + echo "" + echo "Phase 2: Checking AMF logs for registration..." + echo "" + break + fi + + # Show what's happening + if [ $((ELAPSED % 60)) -eq 0 ] && [ $ELAPSED -gt 0 ]; then + echo " Still waiting... (elapsed: ${ELAPSED}s)" + echo " Running containers:" + docker ps --format " {{.Names}} ({{.Status}})" 2>/dev/null || true + else + echo " Waiting... gNB_container=${GNB_CONTAINER_UP} UE_container=${UE_CONTAINER_UP} (elapsed: ${ELAPSED}s)" + fi + + sleep $INTERVAL + ELAPSED=$((ELAPSED + INTERVAL)) +done + +if [ "$GNB_CONTAINER_UP" = false ] || [ "$UE_CONTAINER_UP" = false ]; then + echo "============================================" + echo "[FAILURE] Timeout waiting for containers to start" + echo " gNB container running: ${GNB_CONTAINER_UP}" + echo " UE container running: ${UE_CONTAINER_UP}" + echo "============================================" + echo "" + echo "--- Running containers ---" + docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" 2>/dev/null || true + echo "" + echo "--- Simulation container logs (last 50 lines) ---" + docker logs simulation 2>&1 | tail -50 || true + exit 1 +fi + +# Phase 2: Check AMF logs for registration +while [ $ELAPSED -lt $TIMEOUT ]; do + AMF_LOGS=$(docker logs oai-amf 2>&1 || echo "") + + # Check for gNB registration + if echo "$AMF_LOGS" | grep -qiE "(gNB.*associated|NG_SETUP.*SUCCESS|Connected.*gNB|gNodeB.*context)"; then + if [ "$GNB_REGISTERED" = false ]; then + echo "[PASS] gNB registered with AMF (elapsed: ${ELAPSED}s)" + GNB_REGISTERED=true + fi + fi + + # Check for UE registration + if echo "$AMF_LOGS" | grep -qiE "(registration.*accept|5GMM-REGISTERED|UE.*registered|InitialUEMessage|REGISTRATION_COMPLETE)"; then + if [ "$UE_REGISTERED" = false ]; then + echo "[PASS] UE registered with AMF (elapsed: ${ELAPSED}s)" + UE_REGISTERED=true + fi + fi + + # Both registered - success + if [ "$GNB_REGISTERED" = true ] && [ "$UE_REGISTERED" = true ]; then + echo "" + echo "============================================" + echo "[SUCCESS] Both gNB and UE are registered with the AMF" + echo "============================================" + echo "" + echo "--- Relevant AMF log lines ---" + docker logs oai-amf 2>&1 | grep -iE "(gNB|UE|registration|associated|connected|NGAP)" | tail -20 || true + exit 0 + fi + + echo " Checking AMF... gNB=${GNB_REGISTERED} UE=${UE_REGISTERED} (elapsed: ${ELAPSED}s)" + sleep $INTERVAL + ELAPSED=$((ELAPSED + INTERVAL)) +done + +echo "" +echo "============================================" +echo "[FAILURE] Timeout after ${TIMEOUT}s" +echo " gNB registered: ${GNB_REGISTERED}" +echo " UE registered: ${UE_REGISTERED}" +echo "============================================" +echo "" +echo "--- AMF logs (last 80 lines) ---" +docker logs oai-amf 2>&1 | tail -80 || true +echo "" +echo "--- gNB container logs ---" +docker logs oai-gNB1 2>&1 | tail -30 || true +echo "" +echo "--- UE container logs ---" +docker logs ue_turtlebot 2>&1 | tail -30 || true +echo "" +echo "--- Running containers ---" +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" 2>/dev/null || true +exit 1 diff --git a/tests/integration/docker-compose-test-simulation.yml b/tests/integration/docker-compose-test-simulation.yml new file mode 100644 index 0000000..3dcee73 --- /dev/null +++ b/tests/integration/docker-compose-test-simulation.yml @@ -0,0 +1,44 @@ +version: '3.8' +services: + simulation: + image: simulation:test + privileged: true + container_name: simulation + environment: + - PROJECT_PATH=/app + - DEMO_PROJECT_PATH=${DEMO_PROJECT_PATH} + - ROS_DISCOVERY_SERVER=192.168.70.159:11811 + - IGN_IP=192.168.73.151 + - ROS_DOMAIN_ID=10 + - IGN_PARTITION=domain1 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + stdin_open: true + tty: true + networks: + public_net: + ipv4_address: 192.168.70.152 + ros_gz_net: + ipv4_address: 192.168.73.151 + entrypoint: + - /bin/bash + - -c + - | + source /opt/ros/humble/setup.bash + source /app/gazebo_launch/install/setup.bash + export FASTRTPS_DEFAULT_PROFILES_FILE=/app/dds.xml + PACKAGE_PATH=$$(ros2 pkg prefix ign_turtlebot --share) + export IGN_GAZEBO_RESOURCE_PATH=$${PACKAGE_PATH}/models:/opt/ros/humble/share + export IGN_GAZEBO_SYSTEM_PLUGIN_PATH=$${PACKAGE_PATH}/phine_plugins:/usr/lib/x86_64-linux-gnu/ign-gazebo-6/plugins:/opt/ros/humble/lib + export IGN_GUI_PLUGIN_PATH=$${PACKAGE_PATH}/gui_plugins + ip route add 10.0.0.0/16 via 192.168.70.134 dev eth0 || true + echo "Starting Gazebo in headless (server-only) mode..." + ign gazebo -s -r -v 3 $${PACKAGE_PATH}/worlds/world_only.sdf & + tail -f /dev/null +networks: + public_net: + external: + name: phine-net + ros_gz_net: + external: + name: ros_gz_net