From b6005f486177a6bd20ceb56176747eeaf6748922 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 3 Nov 2026 20:44:45 +0100 Subject: [PATCH 1/2] Probe container's IP and forwarded IP for urcaps endpoint In some situations, we might want to probe for the container's IP, while in other setups we might want to probe the forwarded http port on the host. Before, the host forward was used as soon as it was specified. With this change both are probed and the one that answers with a 200 status code is picked for installing the urcap. --- scripts/start_ursim.sh | 55 ++++++++++++++++++++++---------- tests/test_start_ursim.bats | 62 +++++++++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 19 deletions(-) diff --git a/scripts/start_ursim.sh b/scripts/start_ursim.sh index 977fb16cf..f85dec9da 100755 --- a/scripts/start_ursim.sh +++ b/scripts/start_ursim.sh @@ -404,27 +404,46 @@ post_setup_polyscopex() curl -L -o "$urcapx_file" "$URCAPX_DOWNLOAD_URL" fi - # Prefer a published host mapping for HTTP (needed on Docker Desktop / NAT where the - # container IP is unreachable). Fall back to the container IP when nothing is published. - local forwarded_endpoint http_host - if forwarded_endpoint=$(get_forwarded_access_endpoint 80); then - http_host="$forwarded_endpoint" - else - http_host="$IP_ADDRESS" + # UrService is reachable on the container IP from another container on ursim_net + # (CI). A published host port is required on Docker Desktop, where that IP is + # not routed. Probe both, and install the URCap through whichever answers. + local forwarded_endpoint="" + forwarded_endpoint=$(get_forwarded_access_endpoint 80) || forwarded_endpoint="" + local -a base_urls=("http://${IP_ADDRESS}") + if [[ -n "$forwarded_endpoint" ]]; then + base_urls+=("http://${forwarded_endpoint}") fi - local base_url="http://${http_host}" - local urcaps_url="${base_url}/universal-robots/urservice/api/v1/urcaps" + local urcaps_path="/universal-robots/urservice/api/v1/urcaps" + # Overall wait for UrService. Override with URSIM_URSERVICE_TIMEOUT (seconds). + local urservice_timeout="${URSIM_URSERVICE_TIMEOUT:-300}" + local urcaps_url="" + local base status_code + local deadline=$((SECONDS + urservice_timeout)) echo -ne "Starting URSim. Waiting for UrService to be up..." - status_code=$(curl --retry-connrefused -f --write-out "%{http_code}" --silent --output /dev/null "$urcaps_url") - - until [ "$status_code" == "200" ] - do - sleep 1 - echo -ne "." - status_code=$(curl --retry-connrefused -f --write-out "%{http_code}" --silent --output /dev/null "$urcaps_url") + while [[ -z "$urcaps_url" && "$SECONDS" -lt "$deadline" ]]; do + for base in "${base_urls[@]}"; do + status_code=$( + curl --connect-timeout 2 --max-time 5 --silent --output /dev/null \ + --write-out "%{http_code}" "${base}${urcaps_path}" || true + ) + if [[ "$status_code" == "200" ]]; then + urcaps_url="${base}${urcaps_path}" + break + fi + done + if [[ -z "$urcaps_url" ]]; then + sleep 1 + echo -ne "." + fi done + if [[ -z "$urcaps_url" ]]; then + echo "" + echo "Timed out after ${urservice_timeout}s waiting for UrService. Tried: ${base_urls[*]}" + return 1 + fi + echo ""; echo "UrService is up" echo "Installing URCapX $urcapx_file" @@ -666,7 +685,9 @@ main() { trap "$TRAP_CMD" SIGINT SIGTERM - eval "post_setup_${ROBOT_SERIES}" + if ! "post_setup_${ROBOT_SERIES}"; then + exit 1 + fi if [ "$DETACHED" = false ]; then echo "To exit, press CTRL+C" diff --git a/tests/test_start_ursim.bats b/tests/test_start_ursim.bats index 5c2e802a0..f86e4d050 100644 --- a/tests/test_start_ursim.bats +++ b/tests/test_start_ursim.bats @@ -981,7 +981,7 @@ stub_docker_port() { [[ "$output" != *"Access PolyScope X: http://192."* ]] } -@test "post_setup_polyscopex uses forwarded endpoint for http calls" { +@test "post_setup_polyscopex uses container IP for http calls when it responds" { IP_ADDRESS="192.168.56.101" CONTAINER_NAME="ursim" DOCKER_PORT_MAP='80=127.0.0.1:8000' @@ -1000,15 +1000,73 @@ stub_docker_port() { touch "$URCAP_STORAGE/external-control-0.0.0.urcapx" URSIM_VERSION="10.8.0" + run post_setup_polyscopex + echo "$output" + echo "curl log:"; cat "$CURL_LOG" + [ "$status" -eq 0 ] + grep -F "http://192.168.56.101/universal-robots/urservice/api/v1/urcaps" "$CURL_LOG" + ! grep -F "http://127.0.0.1:8000/universal-robots/urservice/api/v1/urcaps" "$CURL_LOG" + rm -f "$CURL_LOG" +} + +@test "post_setup_polyscopex uses forwarded endpoint when container IP is unreachable" { + IP_ADDRESS="192.168.56.101" + CONTAINER_NAME="ursim" + DOCKER_PORT_MAP='80=127.0.0.1:8000' + stub_docker_port + get_download_url_urcapx() { URCAPX_VERSION="0.0.0"; URCAPX_DOWNLOAD_URL=""; } + CURL_LOG=$(mktemp) + curl() { + printf '%s\n' "$*" >> "$CURL_LOG" + if [[ "$*" == *"--form"* ]]; then + return 0 + fi + if [[ "$*" == *"http://192.168.56.101/universal-robots/urservice/api/v1/urcaps"* ]]; then + echo "000" + return 7 + fi + echo "200" + } + URCAP_STORAGE=/tmp/ursim-test-urcaps-post-setup + mkdir -p "$URCAP_STORAGE" + touch "$URCAP_STORAGE/external-control-0.0.0.urcapx" + URSIM_VERSION="10.8.0" + run post_setup_polyscopex echo "$output" echo "curl log:"; cat "$CURL_LOG" [ "$status" -eq 0 ] grep -F "http://127.0.0.1:8000/universal-robots/urservice/api/v1/urcaps" "$CURL_LOG" - ! grep -F "http://192.168.56.101/universal-robots/urservice/api/v1/urcaps" "$CURL_LOG" rm -f "$CURL_LOG" } +@test "post_setup_polyscopex times out when UrService does not respond" { + IP_ADDRESS="192.168.56.101" + CONTAINER_NAME="ursim" + DOCKER_PORT_MAP='80=127.0.0.1:8000' + stub_docker_port + get_download_url_urcapx() { URCAPX_VERSION="0.0.0"; URCAPX_DOWNLOAD_URL=""; } + curl() { + if [[ "$*" == *"--form"* ]]; then + return 0 + fi + echo "000" + return 7 + } + URCAP_STORAGE=/tmp/ursim-test-urcaps-post-setup + mkdir -p "$URCAP_STORAGE" + touch "$URCAP_STORAGE/external-control-0.0.0.urcapx" + URSIM_VERSION="10.8.0" + URSIM_URSERVICE_TIMEOUT=1 + + run post_setup_polyscopex + echo "$output" + [ "$status" -ne 0 ] + [[ "$output" == *"Timed out after 1s waiting for UrService."* ]] + [[ "$output" == *"http://192.168.56.101"* ]] + [[ "$output" == *"http://127.0.0.1:8000"* ]] +} + @test "post_setup_polyscopex falls back to IP_ADDRESS for http calls when unpublished" { IP_ADDRESS="192.168.56.101" CONTAINER_NAME="ursim" From e144383c277b24625426f71cc12fcc1499084883 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 3 Nov 2026 20:50:05 +0100 Subject: [PATCH 2/2] Fix conditional check and cleanup for Docker container Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/start_ursim.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/start_ursim.sh b/scripts/start_ursim.sh index f85dec9da..cd366057f 100755 --- a/scripts/start_ursim.sh +++ b/scripts/start_ursim.sh @@ -686,6 +686,10 @@ main() { if ! "post_setup_${ROBOT_SERIES}"; then + if [ "$DETACHED" = false ]; then + docker container kill "$CONTAINER_NAME" > /dev/null 2>&1 || true + docker container wait "$CONTAINER_NAME" > /dev/null 2>&1 || true + fi exit 1 fi