Derive the OpenAPI document from the code instead of declaring it alongside #418
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: OPC-UA Plugin Integration | |
| permissions: | |
| contents: read | |
| # Runs the OpenPLC-based Docker integration suite for ros2_medkit_opcua. | |
| # Triggers only when the plugin itself, the gateway plugin/provider API, or | |
| # ros2_medkit_msgs change. A nightly cron provides a safety net against silent | |
| # breaks that slip past the path filter. | |
| on: | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'src/ros2_medkit_plugins/ros2_medkit_opcua/**' | |
| - 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/plugins/**' | |
| - 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/providers/**' | |
| - 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/error_codes.hpp' | |
| - 'src/ros2_medkit_msgs/**' | |
| - '.github/workflows/opcua-plugin.yml' | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'src/ros2_medkit_plugins/ros2_medkit_opcua/**' | |
| - 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/plugins/**' | |
| - 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/providers/**' | |
| - 'src/ros2_medkit_gateway/include/ros2_medkit_gateway/http/error_codes.hpp' | |
| - 'src/ros2_medkit_msgs/**' | |
| - '.github/workflows/opcua-plugin.yml' | |
| schedule: | |
| # Daily at 04:00 UTC - safety net against breaks that slip past the path filter. | |
| - cron: '0 4 * * *' | |
| jobs: | |
| unit-tests: | |
| name: Unit tests (${{ matrix.ros_distro }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - ros_distro: humble | |
| os_image: ubuntu:jammy | |
| - ros_distro: jazzy | |
| os_image: ubuntu:noble | |
| - ros_distro: lyrical | |
| os_image: ubuntu:resolute | |
| continue-on-error: ${{ matrix.ros_distro == 'lyrical' }} | |
| container: | |
| image: ${{ matrix.os_image }} | |
| timeout-minutes: 60 | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Install Git | |
| run: | | |
| apt-get update | |
| apt-get install -y git | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Pre-install ROS 2 apt source | |
| uses: ./.github/actions/ros-apt-source | |
| - name: Set up ROS 2 ${{ matrix.ros_distro }} | |
| uses: ros-tooling/setup-ros@v0.7 | |
| with: | |
| required-ros-distributions: ${{ matrix.ros_distro }} | |
| - name: Install ccache | |
| run: apt-get install -y ccache | |
| - name: Cache ccache | |
| uses: actions/cache@v4 | |
| with: | |
| path: /root/.cache/ccache | |
| key: ccache-opcua-${{ matrix.ros_distro }}-${{ github.sha }} | |
| restore-keys: | | |
| ccache-opcua-${{ matrix.ros_distro }}- | |
| - name: Install dependencies | |
| run: | | |
| apt-get update | |
| apt-get install -y ros-${{ matrix.ros_distro }}-test-msgs libyaml-cpp-dev libssl-dev | |
| source /opt/ros/${{ matrix.ros_distro }}/setup.bash | |
| rosdep update | |
| # Skip nav2_msgs (vda5050_agent declares it; not available on all | |
| # distros) and the linter rosdep keys. Linters only run in the Jazzy | |
| # quality job, so we don't need them installed here. The upstream | |
| # medkit packages have already been switched to QUIET + FOUND for | |
| # those find_package calls so the build degrades gracefully without | |
| # the linter packages installed. | |
| rosdep install --from-paths src --ignore-src -y \ | |
| --skip-keys='nav2_msgs ament_cmake_clang_format ament_cmake_clang_tidy' | |
| - name: Build ros2_medkit_opcua (and upstream deps) | |
| env: | |
| CCACHE_DIR: /root/.cache/ccache | |
| CCACHE_MAXSIZE: 500M | |
| CCACHE_SLOPPINESS: pch_defines,time_macros | |
| run: | | |
| source /opt/ros/${{ matrix.ros_distro }}/setup.bash | |
| # --packages-up-to naturally excludes unrelated packages | |
| # (vda5050_agent, discovery plugins, etc.) because they are not in | |
| # the dependency chain of ros2_medkit_opcua. | |
| colcon build --symlink-install \ | |
| --packages-up-to ros2_medkit_opcua \ | |
| --cmake-args -DCMAKE_BUILD_TYPE=Release \ | |
| --event-handlers console_direct+ | |
| ccache -s | |
| - name: Run unit tests | |
| timeout-minutes: 15 | |
| run: | | |
| source /opt/ros/${{ matrix.ros_distro }}/setup.bash | |
| colcon test --return-code-on-test-failure \ | |
| --packages-select ros2_medkit_opcua \ | |
| --ctest-args -LE linter \ | |
| --event-handlers console_direct+ | |
| - name: Show test results | |
| if: always() | |
| run: colcon test-result --verbose | |
| integration: | |
| name: Integration (OpenPLC) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Build OpenPLC container | |
| run: | | |
| docker build \ | |
| -t openplc-tank \ | |
| src/ros2_medkit_plugins/ros2_medkit_opcua/docker/openplc/ | |
| - name: Build gateway + OPC-UA plugin image | |
| run: | | |
| docker build \ | |
| -f src/ros2_medkit_plugins/ros2_medkit_opcua/docker/Dockerfile.gateway \ | |
| -t gateway-opcua . | |
| - name: Start OpenPLC | |
| timeout-minutes: 3 | |
| run: | | |
| docker network create plc-demo | |
| docker run -d --name openplc --network plc-demo openplc-tank | |
| for i in $(seq 1 60); do | |
| if docker logs openplc 2>&1 | grep -q "PLC State: RUNNING"; then | |
| echo "OpenPLC running after $((i * 2))s" | |
| break | |
| fi | |
| if [ "$i" -eq 60 ]; then | |
| echo "FAIL: OpenPLC did not start" | |
| docker logs openplc 2>&1 | tail -20 | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| - name: Start gateway | |
| run: | | |
| docker run -d --name gateway --network plc-demo -p 8080:8080 \ | |
| -e ROS_DOMAIN_ID=60 \ | |
| -e OPCUA_ENDPOINT_URL="opc.tcp://openplc:4840/openplc/opcua" \ | |
| -e OPCUA_NODE_MAP_PATH="/config/tank_nodes.yaml" \ | |
| gateway-opcua \ | |
| bash -c " | |
| mkdir -p /var/lib/ros2_medkit/rosbags /config | |
| echo 'manifest_version: \"1.0\"' > /config/manifest.yaml | |
| source /opt/ros/jazzy/setup.bash && source /root/ws/install/setup.bash | |
| PLUGIN_PATH=\$(find /root/ws/install -name 'libros2_medkit_opcua_plugin.so' | head -1) | |
| ros2 run ros2_medkit_gateway gateway_node \ | |
| --ros-args --params-file /config/gateway_params.yaml \ | |
| -p plugins.opcua.path:=\$PLUGIN_PATH \ | |
| -p discovery.mode:=hybrid \ | |
| -p discovery.manifest_path:=/config/manifest.yaml \ | |
| -p discovery.manifest_strict_validation:=false" | |
| - name: Wait for PLC entities and live data | |
| timeout-minutes: 3 | |
| run: | | |
| echo "Waiting for entity discovery..." | |
| for i in $(seq 1 60); do | |
| if curl -sf http://localhost:8080/api/v1/apps 2>/dev/null | \ | |
| jq -e '.items | map(.id) | contains(["tank_process"])' >/dev/null 2>&1; then | |
| echo "Entities discovered after $((i * 2))s" | |
| break | |
| fi | |
| if [ "$i" -eq 60 ]; then | |
| echo "Timeout waiting for entities" | |
| docker logs gateway 2>&1 | tail -20 | |
| exit 1 | |
| fi | |
| sleep 2 | |
| done | |
| echo "Waiting for PLC data to flow..." | |
| for i in $(seq 1 30); do | |
| if curl -sf http://localhost:8080/api/v1/components/openplc_runtime/x-plc-status 2>/dev/null | \ | |
| jq -e '.connected == true and .poll_count > 0' >/dev/null 2>&1; then | |
| echo "PLC data flowing after extra $((i * 2))s" | |
| exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "WARNING: PLC not connected yet, running tests anyway" | |
| curl -sf http://localhost:8080/api/v1/components/openplc_runtime/x-plc-status 2>/dev/null | jq . || true | |
| docker logs gateway 2>&1 | tail -10 | |
| - name: Run integration tests | |
| run: bash src/ros2_medkit_plugins/ros2_medkit_opcua/docker/scripts/run_integration_tests.sh | |
| - name: Dump gateway logs on failure | |
| if: failure() | |
| run: | | |
| echo "=== OpenPLC logs ===" | |
| docker logs openplc 2>&1 | tail -60 || true | |
| echo "=== Gateway logs ===" | |
| docker logs gateway 2>&1 | tail -60 || true | |
| - name: Cleanup | |
| if: always() | |
| run: | | |
| docker rm -f gateway openplc 2>/dev/null || true | |
| docker network rm plc-demo 2>/dev/null || true | |
| integration-alarms: | |
| name: Integration (AlarmConditionType) | |
| # Issue #386: tests the native OPC-UA AlarmCondition subscription bridge | |
| # against the test_alarm_server fixture (open62541 with FULL ns0 + alarms | |
| # ON). Independent of the OpenPLC threshold-mode integration above; runs | |
| # in parallel. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install jq + asyncua (smoke test prerequisite) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq python3-pip | |
| pip3 install --break-system-packages asyncua | |
| - name: Run alarm integration suite | |
| run: bash src/ros2_medkit_plugins/ros2_medkit_opcua/docker/scripts/run_alarm_tests.sh | |
| - name: Dump container logs on failure | |
| if: failure() | |
| run: | | |
| for c in alarm-test-server alarm-test-gateway; do | |
| echo "=== ${c} logs ===" | |
| docker logs "${c}" 2>&1 | tail -80 || true | |
| done | |
| integration-secured: | |
| name: Integration (Secured A&C) | |
| # Issue #477: proves the OPC-UA client security code's | |
| # apply_security_config() SUCCESS path - a real Basic256Sha256 + | |
| # SignAndEncrypt SecureChannel with a username token against the | |
| # encryption-enabled test_alarm_server fixture. Needs fault_manager_node | |
| # built alongside the gateway, openssl for cert generation, and | |
| # ROS2_MEDKIT_OPCUA_SECURE_REQUIRE=1 so the test runs for real instead of | |
| # skipping with CTest code 77. | |
| runs-on: ubuntu-latest | |
| container: | |
| image: ubuntu:noble | |
| timeout-minutes: 60 | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Install Git | |
| run: | | |
| apt-get update | |
| apt-get install -y git | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Pre-install ROS 2 apt source | |
| uses: ./.github/actions/ros-apt-source | |
| - name: Set up ROS 2 jazzy | |
| uses: ros-tooling/setup-ros@v0.7 | |
| with: | |
| required-ros-distributions: jazzy | |
| - name: Install ccache | |
| run: apt-get install -y ccache | |
| - name: Cache ccache | |
| uses: actions/cache@v4 | |
| with: | |
| path: /root/.cache/ccache | |
| key: ccache-opcua-secured-${{ github.sha }} | |
| restore-keys: | | |
| ccache-opcua-secured- | |
| - name: Install dependencies | |
| run: | | |
| apt-get update | |
| # openssl: cert generation. libssl-dev: encryption backend for both | |
| # the plugin's open62541pp and the fixture's static open62541. | |
| apt-get install -y ros-jazzy-test-msgs libyaml-cpp-dev libssl-dev openssl | |
| source /opt/ros/jazzy/setup.bash | |
| rosdep update | |
| rosdep install --from-paths src --ignore-src -y \ | |
| --skip-keys='nav2_msgs ament_cmake_clang_format ament_cmake_clang_tidy' | |
| - name: Build gateway + OPC-UA plugin + fault_manager | |
| env: | |
| CCACHE_DIR: /root/.cache/ccache | |
| CCACHE_MAXSIZE: 500M | |
| CCACHE_SLOPPINESS: pch_defines,time_macros | |
| run: | | |
| source /opt/ros/jazzy/setup.bash | |
| # fault_manager_node is a sibling package the secured test launches; | |
| # it is not a build dependency of the plugin, so name it explicitly. | |
| colcon build --symlink-install \ | |
| --packages-up-to ros2_medkit_opcua ros2_medkit_fault_manager \ | |
| --cmake-args -DCMAKE_BUILD_TYPE=Release \ | |
| --event-handlers console_direct+ | |
| ccache -s | |
| - name: Run secured A&C integration test | |
| timeout-minutes: 15 | |
| env: | |
| ROS2_MEDKIT_OPCUA_SECURE_REQUIRE: '1' | |
| run: | | |
| source /opt/ros/jazzy/setup.bash | |
| source install/setup.bash | |
| colcon test --return-code-on-test-failure \ | |
| --packages-select ros2_medkit_opcua \ | |
| --ctest-args -R test_opcua_secured \ | |
| --event-handlers console_direct+ | |
| - name: Show test results | |
| if: always() | |
| run: colcon test-result --verbose |