297 lines
9.5 KiB
Bash
Executable File
297 lines
9.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Run the SIM S0 stock-rover smoke case inside a loopback-only network
|
|
# namespace. The script writes factual runtime artifacts to D and never grants
|
|
# actuator authority.
|
|
|
|
set -Eeuo pipefail
|
|
|
|
readonly EXPECTED_DISTRO="MissionCore-Sim"
|
|
readonly D_ROOT="${MISSIONCORE_SIM_D_ROOT:-/mnt/d/NDC_MISSIONCORE/simulation}"
|
|
readonly PX4_ROOT="${MISSIONCORE_SIM_PX4_ROOT:-${D_ROOT}/source/PX4-Autopilot}"
|
|
readonly AGENT_PREFIX="${MISSIONCORE_SIM_AGENT_PREFIX:-${D_ROOT}/runtime/micro-xrce-dds-agent/2.4.3}"
|
|
readonly PX4_MSGS_SETUP="${MISSIONCORE_SIM_PX4_MSGS_SETUP:-${D_ROOT}/build/ros2/px4_msgs/install/setup.bash}"
|
|
readonly ARTIFACT_ROOT="${MISSIONCORE_SIM_ARTIFACT_ROOT:-${D_ROOT}/artifacts/s0}"
|
|
readonly PROFILE_PATH="${MISSIONCORE_SIM_PROFILE_PATH:-${D_ROOT}/source/qualification-profile.yaml}"
|
|
readonly TIME_PROBE="${MISSIONCORE_SIM_TIME_PROBE:-${D_ROOT}/source/gazebo_time_control.py}"
|
|
readonly RUN_ID="${1:-$(date -u +%Y%m%dT%H%M%SZ)}"
|
|
readonly SPEED_FACTOR="${2:-1}"
|
|
readonly RUN_DIR="${ARTIFACT_ROOT}/${RUN_ID}"
|
|
|
|
if [[ "${SPEED_FACTOR}" != "1" && "${SPEED_FACTOR}" != "2" ]]; then
|
|
echo "error: speed factor must be one of the reviewed values: 1 or 2" >&2
|
|
exit 2
|
|
fi
|
|
|
|
if [[ "${MISSIONCORE_S0_NETNS:-0}" != "1" ]]; then
|
|
if [[ "${EUID}" -ne 0 ]]; then
|
|
echo "error: initial worker-smoke invocation must run as root to create the network namespace" >&2
|
|
exit 2
|
|
fi
|
|
if [[ "${WSL_DISTRO_NAME:-}" != "${EXPECTED_DISTRO}" ]]; then
|
|
echo "error: expected WSL distro ${EXPECTED_DISTRO}, observed ${WSL_DISTRO_NAME:-unset}" >&2
|
|
exit 2
|
|
fi
|
|
|
|
exec unshare --net -- bash -c '
|
|
set -e
|
|
ip link set lo up
|
|
exec runuser -u missioncore -- env MISSIONCORE_S0_NETNS=1 "$@"
|
|
' bash "$0" "$@"
|
|
fi
|
|
|
|
if [[ "${WSL_DISTRO_NAME:-}" != "${EXPECTED_DISTRO}" ]]; then
|
|
echo "error: smoke case escaped the reviewed WSL distribution" >&2
|
|
exit 2
|
|
fi
|
|
|
|
for required_path in "${PX4_ROOT}" "${AGENT_PREFIX}" "${PX4_MSGS_SETUP}" "${ARTIFACT_ROOT}"; do
|
|
if [[ "${required_path}" != /mnt/d/NDC_MISSIONCORE/* ]]; then
|
|
echo "error: mutable or executable path escaped D-only root: ${required_path}" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
readonly AGENT_BIN="${AGENT_PREFIX}/bin/MicroXRCEAgent"
|
|
readonly AGENT_LOG="${RUN_DIR}/micro-xrce-dds-agent.log"
|
|
readonly PX4_LOG="${RUN_DIR}/px4-rover.log"
|
|
readonly BRIDGE_LOG="${RUN_DIR}/ros-gz-clock-bridge.log"
|
|
readonly TOPICS_LOG="${RUN_DIR}/ros-topics.txt"
|
|
readonly VEHICLE_STATUS_LOG="${RUN_DIR}/vehicle-status.yaml"
|
|
readonly CLOCK_LOG="${RUN_DIR}/clock.yaml"
|
|
readonly NETWORK_LOG="${RUN_DIR}/network.txt"
|
|
readonly RESOURCE_LOG="${RUN_DIR}/resource-snapshot.txt"
|
|
readonly GZ_SERVICES_LOG="${RUN_DIR}/gazebo-services.txt"
|
|
readonly GZ_TOPICS_LOG="${RUN_DIR}/gazebo-topics.txt"
|
|
readonly TIME_CONTROL_LOG="${RUN_DIR}/time-control.json"
|
|
readonly RESOURCE_BASELINE_LOG="${RUN_DIR}/resource-baseline.json"
|
|
readonly TIME_PROBE_STDOUT="${RUN_DIR}/time-probe.stdout"
|
|
readonly RESULT_LOG="${RUN_DIR}/result.env"
|
|
|
|
for required_file in \
|
|
"${PX4_ROOT}/build/px4_sitl_default/bin/px4" \
|
|
"${PX4_ROOT}/Tools/simulation/gz/worlds/rover.sdf" \
|
|
"${AGENT_BIN}" \
|
|
"/opt/ros/jazzy/setup.bash" \
|
|
"${PX4_MSGS_SETUP}" \
|
|
"${PROFILE_PATH}" \
|
|
"${TIME_PROBE}"; do
|
|
if [[ ! -e "${required_file}" ]]; then
|
|
echo "error: required S0 runtime input is missing: ${required_file}" >&2
|
|
exit 2
|
|
fi
|
|
done
|
|
|
|
mkdir -p "${RUN_DIR}"
|
|
|
|
agent_pid=""
|
|
px4_pgid=""
|
|
bridge_pgid=""
|
|
|
|
stop_process_group() {
|
|
local pgid="${1:-}"
|
|
if [[ -z "${pgid}" ]]; then
|
|
return
|
|
fi
|
|
if kill -0 -- "-${pgid}" 2>/dev/null; then
|
|
kill -INT -- "-${pgid}" 2>/dev/null || true
|
|
for _ in {1..20}; do
|
|
if ! kill -0 -- "-${pgid}" 2>/dev/null; then
|
|
return
|
|
fi
|
|
sleep 0.25
|
|
done
|
|
kill -TERM -- "-${pgid}" 2>/dev/null || true
|
|
sleep 1
|
|
kill -KILL -- "-${pgid}" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
cleanup() {
|
|
set +e
|
|
exec 3>&-
|
|
stop_process_group "${bridge_pgid}"
|
|
stop_process_group "${px4_pgid}"
|
|
if [[ -n "${agent_pid}" ]] && kill -0 "${agent_pid}" 2>/dev/null; then
|
|
kill -TERM "${agent_pid}" 2>/dev/null
|
|
wait "${agent_pid}" 2>/dev/null
|
|
fi
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
wait_for_pattern() {
|
|
local file="$1"
|
|
local pattern="$2"
|
|
local attempts="$3"
|
|
for ((attempt = 0; attempt < attempts; attempt += 1)); do
|
|
if grep -Fq "${pattern}" "${file}" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
env \
|
|
LD_LIBRARY_PATH="${AGENT_PREFIX}/lib" \
|
|
"${AGENT_BIN}" udp4 -p 8888 -v 4 >"${AGENT_LOG}" 2>&1 &
|
|
agent_pid="$!"
|
|
sleep 1
|
|
if ! kill -0 "${agent_pid}" 2>/dev/null; then
|
|
echo "error: Micro XRCE-DDS Agent exited during startup" >&2
|
|
exit 1
|
|
fi
|
|
|
|
{
|
|
echo "network_namespace=loopback-only"
|
|
ip -brief address
|
|
ss -lunp
|
|
} >"${NETWORK_LOG}"
|
|
|
|
coproc PX4_PROCESS {
|
|
exec setsid env HEADLESS=1 PX4_SIM_SPEED_FACTOR="${SPEED_FACTOR}" make -C "${PX4_ROOT}" \
|
|
px4_sitl gz_rover_ackermann >"${PX4_LOG}" 2>&1
|
|
}
|
|
px4_pgid="${PX4_PROCESS_PID}"
|
|
exec 3>&"${PX4_PROCESS[1]}"
|
|
|
|
if ! wait_for_pattern "${PX4_LOG}" "Startup script returned successfully" 120; then
|
|
echo "error: PX4 stock rover did not reach startup completion" >&2
|
|
exit 1
|
|
fi
|
|
|
|
sleep 3
|
|
printf '%s\n' \
|
|
"ver all" \
|
|
"commander status" >&3
|
|
|
|
if ! wait_for_pattern "${AGENT_LOG}" "session established" 60 \
|
|
|| ! wait_for_pattern \
|
|
"${PX4_LOG}" \
|
|
"successfully created rt/fmu/out/vehicle_status_v1 data writer" \
|
|
60; then
|
|
echo "error: PX4 uXRCE-DDS client did not connect to the loopback agent" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' "uxrce_dds_client status" >&3
|
|
if ! wait_for_pattern "${PX4_LOG}" "Running, connected" 20; then
|
|
echo "error: PX4 did not report the established uXRCE-DDS session" >&2
|
|
exit 1
|
|
fi
|
|
{
|
|
echo
|
|
echo "runtime_listeners=loopback-only-netns"
|
|
ss -lunp
|
|
} >>"${NETWORK_LOG}"
|
|
gz service -l >"${GZ_SERVICES_LOG}"
|
|
gz topic -l >"${GZ_TOPICS_LOG}"
|
|
|
|
set +u
|
|
# shellcheck source=/dev/null
|
|
source /opt/ros/jazzy/setup.bash
|
|
# shellcheck source=/dev/null
|
|
source "${PX4_MSGS_SETUP}"
|
|
set -u
|
|
export ROS_DOMAIN_ID=0
|
|
export ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST
|
|
unset ROS_LOCALHOST_ONLY
|
|
|
|
setsid ros2 run ros_gz_bridge parameter_bridge \
|
|
'/clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock' >"${BRIDGE_LOG}" 2>&1 &
|
|
bridge_pgid="$!"
|
|
|
|
timeout 20s ros2 topic list -t >"${TOPICS_LOG}"
|
|
vehicle_status_topic="/fmu/out/vehicle_status"
|
|
if grep -Fq "/fmu/out/vehicle_status_v1" "${TOPICS_LOG}"; then
|
|
vehicle_status_topic="/fmu/out/vehicle_status_v1"
|
|
fi
|
|
timeout 20s ros2 topic echo --once \
|
|
"${vehicle_status_topic}" px4_msgs/msg/VehicleStatus >"${VEHICLE_STATUS_LOG}"
|
|
timeout 20s ros2 topic echo --once \
|
|
/clock rosgraph_msgs/msg/Clock >"${CLOCK_LOG}"
|
|
|
|
python3 "${TIME_PROBE}" \
|
|
--profile "${PROFILE_PATH}" \
|
|
--speed-factor "${SPEED_FACTOR}" \
|
|
--time-output "${TIME_CONTROL_LOG}" \
|
|
--resource-output "${RESOURCE_BASELINE_LOG}" \
|
|
--storage-root "${D_ROOT}" \
|
|
--owned-pid "${agent_pid}" \
|
|
--owned-pgid "${px4_pgid}" \
|
|
--owned-pgid "${bridge_pgid}" >"${TIME_PROBE_STDOUT}"
|
|
|
|
{
|
|
echo "sample=non-acceptance-point-snapshot"
|
|
free -b
|
|
ps -g "${px4_pgid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
|
|
ps -p "${agent_pid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
|
|
ps -g "${bridge_pgid}" -o pid,ppid,pgid,rss,vsz,pcpu,comm,args
|
|
if command -v nvidia-smi >/dev/null; then
|
|
nvidia-smi \
|
|
--query-gpu=memory.used,utilization.gpu \
|
|
--format=csv,noheader,nounits
|
|
fi
|
|
} >"${RESOURCE_LOG}"
|
|
|
|
printf '%s\n' "shutdown" >&3
|
|
if ! wait_for_pattern "${PX4_LOG}" "Exiting NOW." 20; then
|
|
echo "error: PX4 did not acknowledge the shutdown command" >&2
|
|
exit 1
|
|
fi
|
|
|
|
cleanup
|
|
trap - EXIT INT TERM
|
|
|
|
px4_status="fail"
|
|
dds_status="fail"
|
|
vehicle_status="fail"
|
|
clock_status="fail"
|
|
time_control_status="fail"
|
|
resource_baseline_status="fail"
|
|
lifecycle_status="fail"
|
|
|
|
grep -Fq "world: rover, model: rover_ackermann_0" "${PX4_LOG}" && px4_status="pass"
|
|
if grep -Fq "session established" "${AGENT_LOG}" \
|
|
&& grep -Fq \
|
|
"successfully created rt/fmu/out/vehicle_status_v1 data writer" \
|
|
"${PX4_LOG}"; then
|
|
dds_status="pass"
|
|
fi
|
|
grep -Fq "arming_state:" "${VEHICLE_STATUS_LOG}" && vehicle_status="pass"
|
|
grep -Fq "clock:" "${CLOCK_LOG}" && clock_status="pass"
|
|
grep -Fq '"verdict": "pass"' "${TIME_CONTROL_LOG}" && time_control_status="pass"
|
|
grep -Fq '"verdict": "pass"' "${RESOURCE_BASELINE_LOG}" \
|
|
&& resource_baseline_status="pass"
|
|
if ! pgrep -f "${PX4_ROOT}/build/px4_sitl_default/bin/px4" >/dev/null \
|
|
&& ! pgrep -f "gz sim.*rover.sdf" >/dev/null \
|
|
&& ! pgrep -f "${AGENT_BIN}" >/dev/null; then
|
|
lifecycle_status="pass"
|
|
fi
|
|
|
|
{
|
|
echo "run_id=${RUN_ID}"
|
|
echo "network_scope=loopback-only-netns"
|
|
echo "actuator_authority=false"
|
|
echo "speed_factor=${SPEED_FACTOR}"
|
|
echo "px4_stock_rover=${px4_status}"
|
|
echo "uxrce_dds=${dds_status}"
|
|
echo "ros2_vehicle_status=${vehicle_status}"
|
|
echo "gazebo_clock=${clock_status}"
|
|
echo "pause_step_speed=${time_control_status}"
|
|
echo "resource_baseline=${resource_baseline_status}"
|
|
echo "clean_lifecycle=${lifecycle_status}"
|
|
} >"${RESULT_LOG}"
|
|
|
|
if [[ "${px4_status}" != "pass" \
|
|
|| "${dds_status}" != "pass" \
|
|
|| "${vehicle_status}" != "pass" \
|
|
|| "${clock_status}" != "pass" \
|
|
|| "${time_control_status}" != "pass" \
|
|
|| "${resource_baseline_status}" != "pass" \
|
|
|| "${lifecycle_status}" != "pass" ]]; then
|
|
echo "error: one or more S0 smoke checks failed; inspect ${RUN_DIR}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "SIM S0 smoke PASS: ${RUN_DIR}"
|