Skip to content

Commit 26a442b

Browse files
Merge branch 'develop' into antoiner/feat/ovphysx_rigidobject
2 parents 8bc3a4a + 530fa8b commit 26a442b

474 files changed

Lines changed: 25096 additions & 4076 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/run-package-tests/action.yml

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ inputs:
5555
description: 'Additional pytest options'
5656
default: ''
5757
required: false
58+
extra-pip-packages:
59+
description: 'Space-separated pip packages to install inside the Docker container before pytest starts'
60+
default: ''
61+
required: false
5862
container-name:
5963
description: 'Docker container name prefix (run-id is appended automatically)'
6064
required: true
@@ -83,6 +87,15 @@ runs:
8387
echo "🟠 Could not obtain IMDS token, probably not running on EC2"
8488
fi
8589
90+
- name: Prepare Docker disk space
91+
shell: bash
92+
run: |
93+
bash .github/actions/run-package-tests/cleanup_docker_storage.sh \
94+
"${{ inputs.isaacsim-base-image }}:${{ inputs.isaacsim-version }}" \
95+
"isaacsim-pin-${{ inputs.container-name }}-${{ github.run_id }}-${{ github.run_attempt }}-pre" \
96+
"pre-test-cleanup" \
97+
"true"
98+
8699
- name: Record pull start time
87100
id: pull-start
88101
shell: bash
@@ -125,6 +138,7 @@ runs:
125138
quarantined-only: ${{ inputs.quarantined-only }}
126139
include-files: ${{ inputs.include-files }}
127140
volume-mount-source: ${{ github.workspace }}
141+
extra-pip-packages: ${{ inputs.extra-pip-packages }}
128142

129143
- name: Check Test Results
130144
if: always()
@@ -153,28 +167,8 @@ runs:
153167
if: always()
154168
shell: bash
155169
run: |
156-
# Don't let cleanup errors fail a passing test job
157-
set +e
158-
159-
echo "🔵 Cleaning up old Docker images..."
160-
161-
echo "⚪ Disk usage before cleanup:"
162-
df -h /var/lib/docker
163-
164-
ISAACSIM_IMAGE="${{ inputs.isaacsim-base-image }}:${{ inputs.isaacsim-version }}"
165-
166-
# Pin the base IsaacSim image so prune -a doesn't remove it
167-
ISAACSIM_IMAGE_PIN_CONTAINER="isaacsim-pin-${{ inputs.container-name }}-${{ github.run_id }}-${{ github.run_attempt }}"
168-
echo "🔵 Pinning IsaacSim base image: ${ISAACSIM_IMAGE} to temporary container ${ISAACSIM_IMAGE_PIN_CONTAINER}"
169-
docker create --name "${ISAACSIM_IMAGE_PIN_CONTAINER}" "${ISAACSIM_IMAGE}" true 2>/dev/null || true
170-
171-
echo "🔵 Removing Docker images older than 3 days..."
172-
docker image prune -a -f --filter "until=72h"
173-
174-
echo "🔵 Removing temporary container ${ISAACSIM_IMAGE_PIN_CONTAINER}..."
175-
docker rm "${ISAACSIM_IMAGE_PIN_CONTAINER}" 2>/dev/null || true
176-
177-
echo "🟢 Docker image cleanup complete."
178-
179-
echo "⚪ Disk usage after cleanup:"
180-
df -h /var/lib/docker
170+
bash .github/actions/run-package-tests/cleanup_docker_storage.sh \
171+
"${{ inputs.isaacsim-base-image }}:${{ inputs.isaacsim-version }}" \
172+
"isaacsim-pin-${{ inputs.container-name }}-${{ github.run_id }}-${{ github.run_attempt }}-post" \
173+
"post-job-cleanup" \
174+
"false"
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env bash
2+
3+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
4+
# All rights reserved.
5+
#
6+
# SPDX-License-Identifier: BSD-3-Clause
7+
8+
set -u
9+
10+
ISAACSIM_IMAGE="${1:-}"
11+
ISAACSIM_IMAGE_PIN_CONTAINER="${2:-isaacsim-pin-run-package-tests}"
12+
CLEANUP_CONTEXT="${3:-docker-cleanup}"
13+
FAIL_IF_STILL_HIGH="${4:-false}"
14+
15+
DOCKER_STORAGE_PATH="${DOCKER_STORAGE_PATH:-/var/lib/docker}"
16+
DOCKER_CLEANUP_THRESHOLD_PERCENT="${DOCKER_CLEANUP_THRESHOLD_PERCENT:-85}"
17+
DOCKER_AGGRESSIVE_PRUNE_UNTIL="${DOCKER_AGGRESSIVE_PRUNE_UNTIL:-24h}"
18+
DOCKER_CONSERVATIVE_PRUNE_UNTIL="${DOCKER_CONSERVATIVE_PRUNE_UNTIL:-72h}"
19+
20+
get_docker_storage_usage_percent() {
21+
df -P "${DOCKER_STORAGE_PATH}" 2>/dev/null | awk 'NR == 2 { gsub("%", "", $5); print $5 }'
22+
}
23+
24+
resolve_docker_storage_path() {
25+
local docker_root_dir
26+
docker_root_dir="$(docker info --format '{{.DockerRootDir}}' 2>/dev/null || true)"
27+
if [ -n "${docker_root_dir}" ]; then
28+
DOCKER_STORAGE_PATH="${docker_root_dir}"
29+
echo "[${CLEANUP_CONTEXT}] Using Docker root from docker info: ${DOCKER_STORAGE_PATH}"
30+
return 0
31+
fi
32+
33+
echo "[${CLEANUP_CONTEXT}] Could not determine Docker storage path from ${DOCKER_STORAGE_PATH} or docker info." >&2
34+
return 1
35+
}
36+
37+
print_docker_storage_usage() {
38+
echo "[${CLEANUP_CONTEXT}] Docker storage usage:"
39+
df -h "${DOCKER_STORAGE_PATH}" || true
40+
docker system df || true
41+
}
42+
43+
pin_isaacsim_image_if_present() {
44+
if [ -z "${ISAACSIM_IMAGE}" ]; then
45+
echo "[${CLEANUP_CONTEXT}] No IsaacSim base image provided; skipping image pin."
46+
return
47+
fi
48+
49+
if docker image inspect "${ISAACSIM_IMAGE}" >/dev/null 2>&1; then
50+
echo "[${CLEANUP_CONTEXT}] Pinning IsaacSim base image: ${ISAACSIM_IMAGE}"
51+
docker create --name "${ISAACSIM_IMAGE_PIN_CONTAINER}" "${ISAACSIM_IMAGE}" true >/dev/null 2>&1 || true
52+
else
53+
echo "[${CLEANUP_CONTEXT}] IsaacSim base image is not present locally; skipping image pin."
54+
fi
55+
}
56+
57+
remove_isaacsim_image_pin() {
58+
docker rm "${ISAACSIM_IMAGE_PIN_CONTAINER}" >/dev/null 2>&1 || true
59+
}
60+
61+
run_conservative_cleanup() {
62+
echo "[${CLEANUP_CONTEXT}] Running conservative Docker cleanup."
63+
echo "[${CLEANUP_CONTEXT}] Removing Docker images older than ${DOCKER_CONSERVATIVE_PRUNE_UNTIL}."
64+
docker image prune -a -f --filter "until=${DOCKER_CONSERVATIVE_PRUNE_UNTIL}" || true
65+
}
66+
67+
run_aggressive_cleanup() {
68+
echo "[${CLEANUP_CONTEXT}] Running aggressive Docker cleanup."
69+
echo "[${CLEANUP_CONTEXT}] Removing stopped Docker containers."
70+
docker container prune -f --filter "until=${DOCKER_AGGRESSIVE_PRUNE_UNTIL}" || true
71+
72+
echo "[${CLEANUP_CONTEXT}] Removing unused Docker builder cache older than ${DOCKER_AGGRESSIVE_PRUNE_UNTIL}."
73+
docker builder prune -a -f --filter "until=${DOCKER_AGGRESSIVE_PRUNE_UNTIL}" || true
74+
75+
echo "[${CLEANUP_CONTEXT}] Removing Docker images older than ${DOCKER_AGGRESSIVE_PRUNE_UNTIL}."
76+
docker image prune -a -f --filter "until=${DOCKER_AGGRESSIVE_PRUNE_UNTIL}" || true
77+
}
78+
79+
main() {
80+
echo "[${CLEANUP_CONTEXT}] Checking Docker storage before cleanup."
81+
82+
local usage_percent
83+
usage_percent="$(get_docker_storage_usage_percent)"
84+
if [ -z "${usage_percent}" ]; then
85+
resolve_docker_storage_path || true
86+
usage_percent="$(get_docker_storage_usage_percent)"
87+
fi
88+
89+
print_docker_storage_usage
90+
91+
if [ -z "${usage_percent}" ]; then
92+
echo "[${CLEANUP_CONTEXT}] Could not determine Docker storage usage; running conservative cleanup."
93+
pin_isaacsim_image_if_present
94+
trap remove_isaacsim_image_pin EXIT
95+
run_conservative_cleanup
96+
print_docker_storage_usage
97+
if [ "${FAIL_IF_STILL_HIGH}" = "true" ]; then
98+
echo "::error::Could not determine Docker storage usage after cleanup. The self-hosted runner needs a measurable Docker storage path before tests can run." >&2
99+
return 1
100+
fi
101+
return 0
102+
fi
103+
104+
pin_isaacsim_image_if_present
105+
trap remove_isaacsim_image_pin EXIT
106+
107+
if [ "${usage_percent}" -ge "${DOCKER_CLEANUP_THRESHOLD_PERCENT}" ]; then
108+
echo "[${CLEANUP_CONTEXT}] Docker storage is at ${usage_percent}%, meeting the ${DOCKER_CLEANUP_THRESHOLD_PERCENT}% threshold."
109+
run_aggressive_cleanup
110+
else
111+
echo "[${CLEANUP_CONTEXT}] Docker storage is at ${usage_percent}%, below the ${DOCKER_CLEANUP_THRESHOLD_PERCENT}% threshold."
112+
run_conservative_cleanup
113+
fi
114+
115+
echo "[${CLEANUP_CONTEXT}] Docker storage after cleanup."
116+
print_docker_storage_usage
117+
118+
local final_usage_percent
119+
final_usage_percent="$(get_docker_storage_usage_percent)"
120+
if [ "${FAIL_IF_STILL_HIGH}" = "true" ] \
121+
&& [ -n "${final_usage_percent}" ] \
122+
&& [ "${final_usage_percent}" -ge "${DOCKER_CLEANUP_THRESHOLD_PERCENT}" ]; then
123+
echo "::error::Docker storage remains at ${final_usage_percent}% after cleanup. The self-hosted runner needs more free Docker storage before tests can run." >&2
124+
return 1
125+
fi
126+
}
127+
128+
main "$@"
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Tests for CI Docker storage cleanup helpers."""
7+
8+
from __future__ import annotations
9+
10+
import os
11+
import subprocess
12+
from pathlib import Path
13+
14+
_ACTION_DIR = Path(__file__).resolve().parent
15+
_REPO_ROOT = _ACTION_DIR.parents[2]
16+
_ACTION_PATH = _ACTION_DIR / "action.yml"
17+
_SCRIPT_PATH = _ACTION_DIR / "cleanup_docker_storage.sh"
18+
19+
20+
def _write_fake_df(
21+
bin_dir: Path, first_usage: int, final_usage: int, failed_path: str | None = None, fallback_path: str | None = None
22+
) -> None:
23+
count_file = bin_dir / "df-count"
24+
count_file.write_text("0", encoding="utf-8")
25+
script = bin_dir / "df"
26+
script.write_text(
27+
f"""#!/usr/bin/env bash
28+
set -euo pipefail
29+
30+
path="${{2:-}}"
31+
if [ -n "{failed_path or ""}" ] && [ "$path" = "{failed_path or ""}" ]; then
32+
exit 1
33+
fi
34+
if [ -n "{fallback_path or ""}" ] && [ "$path" = "{fallback_path or ""}" ]; then
35+
:
36+
elif [ -n "{fallback_path or ""}" ]; then
37+
exit 1
38+
fi
39+
40+
if [ "${{1:-}}" = "-P" ]; then
41+
count=$(cat "{count_file}")
42+
if [ "$count" -eq 0 ]; then
43+
usage="{first_usage}"
44+
else
45+
usage="{final_usage}"
46+
fi
47+
echo "$((count + 1))" > "{count_file}"
48+
echo "Filesystem 1024-blocks Used Available Capacity Mounted on"
49+
echo "/dev/test 100 90 10 ${{usage}}% /var/lib/docker"
50+
else
51+
echo "Filesystem Size Used Avail Use% Mounted on"
52+
echo "/dev/test 100G 90G 10G {first_usage}% /var/lib/docker"
53+
fi
54+
""",
55+
encoding="utf-8",
56+
)
57+
script.chmod(0o755)
58+
59+
60+
def _write_fake_docker(bin_dir: Path, docker_root_dir: str = "/var/lib/docker") -> Path:
61+
log_path = bin_dir / "docker.log"
62+
script = bin_dir / "docker"
63+
script.write_text(
64+
f"""#!/usr/bin/env bash
65+
set -euo pipefail
66+
67+
printf '%s\\n' "$*" >> "{log_path}"
68+
if [ "${{1:-}}" = "info" ] && [ "${{2:-}}" = "--format" ]; then
69+
if [ -z "{docker_root_dir}" ]; then
70+
exit 1
71+
fi
72+
echo "{docker_root_dir}"
73+
exit 0
74+
fi
75+
if [ "${{1:-}}" = "image" ] && [ "${{2:-}}" = "inspect" ]; then
76+
exit 0
77+
fi
78+
exit 0
79+
""",
80+
encoding="utf-8",
81+
)
82+
script.chmod(0o755)
83+
return log_path
84+
85+
86+
def _run_cleanup(
87+
tmp_path: Path,
88+
first_usage: int,
89+
final_usage: int,
90+
failed_path: str | None = None,
91+
docker_root_dir: str = "/var/lib/docker",
92+
) -> subprocess.CompletedProcess[str]:
93+
bin_dir = tmp_path / "bin"
94+
bin_dir.mkdir()
95+
_write_fake_df(bin_dir, first_usage, final_usage, failed_path=failed_path, fallback_path=docker_root_dir)
96+
_write_fake_docker(bin_dir, docker_root_dir=docker_root_dir)
97+
98+
env = os.environ.copy()
99+
env["PATH"] = f"{bin_dir}{os.pathsep}{env['PATH']}"
100+
env["DOCKER_STORAGE_PATH"] = "/var/lib/docker"
101+
env["DOCKER_CLEANUP_THRESHOLD_PERCENT"] = "85"
102+
103+
return subprocess.run(
104+
[
105+
"bash",
106+
str(_SCRIPT_PATH),
107+
"nvcr.io/nvidian/isaac-sim:latest-develop",
108+
"isaacsim-pin-test",
109+
"test-cleanup",
110+
"true",
111+
],
112+
cwd=_REPO_ROOT,
113+
env=env,
114+
check=False,
115+
capture_output=True,
116+
text=True,
117+
)
118+
119+
120+
def test_cleanup_uses_aggressive_pruning_when_docker_storage_exceeds_threshold(tmp_path: Path):
121+
result = _run_cleanup(tmp_path, first_usage=91, final_usage=70)
122+
123+
assert result.returncode == 0, result.stderr
124+
docker_log = (tmp_path / "bin" / "docker.log").read_text(encoding="utf-8")
125+
assert "container prune -f --filter until=24h" in docker_log
126+
assert "builder prune -a -f --filter until=24h" in docker_log
127+
assert "image prune -a -f --filter until=24h" in docker_log
128+
129+
130+
def test_cleanup_keeps_conservative_image_pruning_below_threshold(tmp_path: Path):
131+
result = _run_cleanup(tmp_path, first_usage=70, final_usage=70)
132+
133+
assert result.returncode == 0, result.stderr
134+
docker_log = (tmp_path / "bin" / "docker.log").read_text(encoding="utf-8")
135+
assert "image prune -a -f --filter until=72h" in docker_log
136+
assert "builder prune" not in docker_log
137+
138+
139+
def test_cleanup_fails_early_when_aggressive_pruning_cannot_recover_space(tmp_path: Path):
140+
result = _run_cleanup(tmp_path, first_usage=91, final_usage=89)
141+
142+
assert result.returncode == 1
143+
assert "Docker storage remains at 89%" in result.stderr
144+
145+
146+
def test_cleanup_uses_docker_root_dir_when_default_storage_path_is_unavailable(tmp_path: Path):
147+
result = _run_cleanup(
148+
tmp_path,
149+
first_usage=91,
150+
final_usage=89,
151+
failed_path="/var/lib/docker",
152+
docker_root_dir="/mnt/docker-root",
153+
)
154+
155+
assert result.returncode == 1
156+
assert "Using Docker root from docker info: /mnt/docker-root" in result.stdout
157+
assert "Docker storage remains at 89%" in result.stderr
158+
159+
160+
def test_cleanup_fails_early_when_docker_storage_usage_cannot_be_measured(tmp_path: Path):
161+
result = _run_cleanup(
162+
tmp_path,
163+
first_usage=91,
164+
final_usage=89,
165+
failed_path="/var/lib/docker",
166+
docker_root_dir="",
167+
)
168+
169+
assert result.returncode == 1
170+
assert "Could not determine Docker storage usage after cleanup" in result.stderr
171+
172+
173+
def test_run_package_tests_prepares_docker_space_before_image_pull():
174+
action_text = _ACTION_PATH.read_text(encoding="utf-8")
175+
176+
cleanup_index = action_text.index("name: Prepare Docker disk space")
177+
pull_start_index = action_text.index("name: Record pull start time")
178+
pull_index = action_text.index("name: Pull image from ECR")
179+
180+
assert cleanup_index < pull_start_index < pull_index
181+
assert "cleanup_docker_storage.sh" in action_text

0 commit comments

Comments
 (0)