Skip to content

Commit 71384d3

Browse files
first iteration of NRI tests
1 parent 4b69ca8 commit 71384d3

452 files changed

Lines changed: 129845 additions & 1 deletion

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/workflows/containerd.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,19 @@ jobs:
305305
runtime_type = \"${{matrix.runtime}}\"
306306
EOF'
307307
308+
# Enable NRI for containerd 2.x (main branch only)
309+
if [[ "${{ matrix.version }}" == "main" ]]; then
310+
sudo bash -c 'cat >> ${BDIR}/config.toml <<EOF
311+
[plugins."io.containerd.nri.v1.nri"]
312+
disable = false
313+
disable_connections = false
314+
plugin_path = "/opt/nri/plugins"
315+
socket_path = "/var/run/nri/nri.sock"
316+
EOF'
317+
sudo mkdir -p /var/run/nri
318+
sudo mkdir -p /opt/nri/plugins
319+
fi
320+
308321
# Remove possibly existing containerd configuration
309322
sudo rm -rf /etc/containerd
310323
sudo PATH=$PATH bash -c "/usr/local/bin/containerd -a ${BDIR}/c.sock -root ${BDIR}/root -state ${BDIR}/state -log-level debug &> ${BDIR}/containerd-cri.log &"
@@ -317,8 +330,14 @@ jobs:
317330
sudo /usr/local/sbin/runc --version
318331
sudo mount
319332
333+
# Build critest flags
334+
CRITEST_FLAGS="--ginkgo.v --runtime-endpoint=unix:///${BDIR}/c.sock --parallel=8"
335+
if [[ "${{ matrix.version }}" == "main" ]]; then
336+
CRITEST_FLAGS="${CRITEST_FLAGS} --nri-socket=/var/run/nri/nri.sock"
337+
fi
338+
320339
TEST_RC=0
321-
sudo -E PATH=$PATH critest --ginkgo.v --runtime-endpoint=unix:///${BDIR}/c.sock --parallel=8 || TEST_RC=$?
340+
sudo -E PATH=$PATH critest ${CRITEST_FLAGS} || TEST_RC=$?
322341
test $TEST_RC -ne 0 && cat ${BDIR}/containerd-cri.log
323342
sudo pkill containerd
324343
echo "CONTD_CRI_DIR=$BDIR" >> $GITHUB_ENV

.github/workflows/crio.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ jobs:
5858
sudo mkdir -p /etc/crio/crio.conf.d
5959
printf '[crio.runtime]\nlog_level = "debug"\n[crio.image]\nshort_name_mode = "disabled"\n' | sudo tee /etc/crio/crio.conf.d/01-log-level.conf
6060
61+
- name: Configure NRI for CRI-O
62+
run: |
63+
sudo mkdir -p /var/run/nri /opt/nri/plugins
64+
sudo tee /etc/crio/crio.conf.d/05-nri.conf <<EOF
65+
[crio.nri]
66+
enable_nri = true
67+
nri_listen = "/var/run/nri/nri.sock"
68+
nri_plugin_dir = "/opt/nri/plugins"
69+
EOF
70+
6171
- name: Configure CRI-O to use conmon-rs instead of the default conmon
6272
if: ${{matrix.monitor == 'conmon-rs'}}
6373
run: |
@@ -91,6 +101,7 @@ jobs:
91101
set +o errexit
92102
sudo -E PATH=$PATH critest \
93103
--runtime-endpoint=unix:///var/run/crio/crio.sock \
104+
--nri-socket=/var/run/nri/nri.sock \
94105
--parallel=$(nproc) \
95106
--ginkgo.flake-attempts=3 \
96107
--ginkgo.randomize-all \

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ test-critest-containerd: ## Run the critest in a container with containerd.
210210
# AppArmor tests must be skipped as the containerized environment does not support them.
211211
hack/run-e2e-container.sh /usr/local/bin/critest-tools/critest \
212212
--runtime-endpoint=unix:///run/containerd/containerd.sock \
213+
--nri-socket=/var/run/nri/nri.sock \
213214
--ginkgo.vv \
214215
--ginkgo.skip="AppArmor" \
215216
$(TESTFLAGS)

docs/nri-spec-discrepancies.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# NRI Spec Discrepancies
2+
3+
This document tracks cases where observed runtime behavior differs from the
4+
NRI Pod Sandbox Lifecycle specification (containerd/nri#286).
5+
6+
## StopPodSandbox idempotency — sandbox reuse after Stop
7+
8+
**Spec requirement** (from containerd/nri#286): After StopPodSandbox, the
9+
sandbox is never reused — CreateContainer on a stopped sandbox MUST fail.
10+
11+
**Observed behavior**: containerd allows CreateContainer to succeed on a
12+
stopped sandbox, returning a valid container ID instead of an error.
13+
14+
**Contract points in this scenario:**
15+
16+
- [x] StopPodSandbox is idempotent (multiple calls succeed without error)
17+
- [x] StopPodSandbox NRI hook fires at least once
18+
- [ ] CreateContainer on a stopped sandbox fails ← discrepancy
19+
20+
**Impact**: A plugin that assumes stopped sandboxes are immutable may see
21+
unexpected container lifecycle events after StopPodSandbox.
22+
23+
**Status**: Open
24+
25+
## Multi-plugin teardown hook delivery after plugin failure
26+
27+
**Spec requirement** (from containerd/nri#286): One plugin's failure on
28+
StopPodSandbox/RemovePodSandbox MUST NOT prevent delivery of those hooks to
29+
subsequent plugins.
30+
31+
**Observed behavior**: NRI aborts hook delivery to remaining plugins when one
32+
plugin returns an error. Plugin 1 (higher index) receives zero
33+
StopPodSandbox/RemovePodSandbox hooks when plugin 0 (lower index) returns an
34+
error.
35+
36+
**Contract points in this scenario:**
37+
38+
- [x] Plugin 0 receives StopPodSandbox hook
39+
- [x] Plugin 0 receives RemovePodSandbox hook
40+
- [ ] Plugin 1 receives StopPodSandbox hook despite plugin 0's error ← discrepancy
41+
- [ ] Plugin 1 receives RemovePodSandbox hook despite plugin 0's error ← discrepancy
42+
- [x] StopPodSandbox CRI call succeeds despite plugin error
43+
- [x] RemovePodSandbox CRI call succeeds despite plugin error
44+
- [x] Sandbox is fully removed
45+
46+
**Impact**: A lower-index plugin returning an error on teardown silently
47+
prevents all higher-index plugins from receiving teardown notifications,
48+
potentially causing resource leaks in those plugins.
49+
50+
**Status**: Open
51+
52+
## containerd: NRI plugin errors swallowed on StopPodSandbox CRI call
53+
54+
**Spec requirement**: StopPodSandbox and RemovePodSandbox CRI calls MUST
55+
propagate NRI plugin errors to the CRI caller so the caller is aware of
56+
the failure.
57+
58+
**Observed behavior** (containerd): When an NRI plugin returns an error on
59+
StopPodSandbox or RemovePodSandbox, containerd swallows the error and
60+
returns success to the CRI caller. CRI-O correctly propagates the error.
61+
62+
**Affected runtimes**: containerd (CRI-O handles this correctly)
63+
64+
**Impact**: The CRI caller is unaware that an NRI plugin failed during
65+
teardown, potentially masking resource leaks or plugin state corruption.
66+
67+
**Status**: Open
68+
69+
## CRI-O: empty container name in NRI CreateContainer event metadata
70+
71+
**Spec requirement**: NRI CreateContainer events should include the container
72+
name in the event metadata so plugins can identify containers.
73+
74+
**Observed behavior** (CRI-O): The `ContainerName` field in NRI
75+
CreateContainer events is an empty string.
76+
77+
**Affected runtimes**: CRI-O (containerd populates this correctly)
78+
79+
**Impact**: Plugins that rely on container name for policy decisions cannot
80+
identify containers during CreateContainer hooks on CRI-O.
81+
82+
**Status**: Open

docs/validation.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,55 @@ critest connects to Unix: `unix:///run/containerd/containerd.sock` or Windows: `
101101
- `-ginkgo.no-color`: Suppress color output in default reporter.
102102
- `-ginkgo.trace`: Print out the full stack trace when a failure occurs.
103103

104+
### NRI (Node Resource Interface) Testing
105+
106+
- `-nri-socket`: Path to the NRI socket (e.g., `/var/run/nri/nri.sock`). When set, NRI integration tests are enabled. When not set, NRI tests are skipped.
107+
108+
NRI tests validate runtime behavior when NRI plugins are active. They verify plugin lifecycle hooks (RunPodSandbox, StopPodSandbox, RemovePodSandbox, CreateContainer, StartContainer, StopContainer, RemoveContainer), failure handling, retry semantics, blocking behavior, and multi-plugin ordering guarantees.
109+
110+
NRI tests are Linux-only and marked `[Serial]` because they register NRI plugins that affect runtime-wide behavior.
111+
112+
#### Running NRI tests against a local runtime
113+
114+
The following are example configurations. Refer to the official documentation
115+
for your container runtime for complete and up-to-date setup instructions:
116+
117+
- [NRI repository](https://github.com/containerd/nri) for NRI specification and configuration reference
118+
- [containerd documentation](https://github.com/containerd/containerd/blob/main/docs/NRI.md) for containerd NRI setup
119+
- [CRI-O documentation](https://github.com/cri-o/cri-o/blob/main/docs/crio.conf.5.md) for CRI-O NRI configuration
120+
121+
Example containerd config with NRI enabled:
122+
123+
```toml
124+
[plugins."io.containerd.nri.v1.nri"]
125+
disable = false
126+
disable_connections = false
127+
plugin_path = "/opt/nri/plugins"
128+
socket_path = "/var/run/nri/nri.sock"
129+
```
130+
131+
Create the NRI socket directory and restart containerd:
132+
133+
```sh
134+
sudo mkdir -p /var/run/nri
135+
sudo systemctl restart containerd
136+
```
137+
138+
Then run critest with the NRI socket flag:
139+
140+
```sh
141+
critest --nri-socket=/var/run/nri/nri.sock
142+
```
143+
144+
For CRI-O, add an NRI config drop-in at `/etc/crio/crio.conf.d/10-nri.conf`:
145+
146+
```ini
147+
[crio.nri]
148+
enable_nri = true
149+
nri_listen = "/var/run/nri/nri.sock"
150+
nri_plugin_dir = "/opt/nri/plugins"
151+
```
152+
104153
### Other Options
105154

106155
- `-version`: Display version of critest.

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module sigs.k8s.io/cri-tools
33
go 1.26.2
44

55
require (
6+
github.com/containerd/nri v0.12.0
67
github.com/distribution/reference v0.6.0
78
github.com/docker/go-units v0.5.0
89
github.com/google/uuid v1.6.0
@@ -41,6 +42,8 @@ require (
4142
github.com/buger/jsonparser v1.1.2 // indirect
4243
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
4344
github.com/cespare/xxhash/v2 v2.3.0 // indirect
45+
github.com/containerd/log v0.1.0 // indirect
46+
github.com/containerd/ttrpc v1.2.7 // indirect
4447
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
4548
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
4649
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -54,14 +57,17 @@ require (
5457
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
5558
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
5659
github.com/json-iterator/go v1.1.12 // indirect
60+
github.com/knqyf263/go-plugin v0.9.0 // indirect
5761
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
5862
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
5963
github.com/moby/spdystream v0.5.1 // indirect
6064
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6165
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
6266
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
67+
github.com/opencontainers/runtime-spec v1.3.0 // indirect
6368
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
6469
github.com/russross/blackfriday/v2 v2.1.0 // indirect
70+
github.com/tetratelabs/wazero v1.11.0 // indirect
6571
github.com/x448/float16 v0.8.4 // indirect
6672
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
6773
go.opentelemetry.io/auto/sdk v1.2.1 // indirect

go.sum

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
1010
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
1111
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
1212
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
13+
github.com/brianvoe/gofakeit/v7 v7.12.1 h1:df1tiI4SL1dR5Ix4D/r6a3a+nXBJ/OBGU5jEKRBmmqg=
14+
github.com/brianvoe/gofakeit/v7 v7.12.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
1315
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
1416
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
1517
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
1618
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
1719
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
1820
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
21+
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
22+
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
23+
github.com/containerd/nri v0.12.0 h1:RvZtyCM64XOB1UmMAFOlfReTTwCd+hE2IEQ5XEpkKA0=
24+
github.com/containerd/nri v0.12.0/go.mod h1:TGAfPLH4a+qwbv0PxsefPiR+PobYecDj2aXMtz7GQcg=
25+
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=
26+
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
1927
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
2028
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2129
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
@@ -80,6 +88,8 @@ github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE
8088
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
8189
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
8290
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
91+
github.com/knqyf263/go-plugin v0.9.0 h1:CQs2+lOPIlkZVtcb835ZYDEoyyWJWLbSTWeCs0EwTwI=
92+
github.com/knqyf263/go-plugin v0.9.0/go.mod h1:2z5lCO1/pez6qGo8CvCxSlBFSEat4MEp1DrnA+f7w8Q=
8393
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
8494
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
8595
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -112,13 +122,17 @@ github.com/onsi/gomega v1.40.0 h1:Vtol0e1MghCD2ZVIilPDIg44XSL9l2QAn8ZNaljWcJc=
112122
github.com/onsi/gomega v1.40.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A=
113123
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
114124
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
125+
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
126+
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
115127
github.com/opencontainers/selinux v1.14.0 h1:k1w6YWg3w/TvfZUAc3ksdaRwGNulRbE88TxqAZxUSOE=
116128
github.com/opencontainers/selinux v1.14.0/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ=
117129
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
118130
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
119131
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
120132
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
121133
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
134+
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
135+
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
122136
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
123137
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
124138
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -133,6 +147,8 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
133147
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
134148
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
135149
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
150+
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
151+
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
136152
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
137153
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
138154
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=

hack/setup-containerd.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,17 @@ version = 2
3535
runtime_type = "${RUNTIME}"
3636
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
3737
SystemdCgroup = false
38+
[plugins."io.containerd.nri.v1.nri"]
39+
disable = false
40+
disable_connections = false
41+
plugin_path = "/opt/nri/plugins"
42+
socket_path = "/var/run/nri/nri.sock"
3843
EOF
3944

45+
echo "Setting up NRI socket directory..."
46+
mkdir -p /var/run/nri
47+
mkdir -p /opt/nri/plugins
48+
4049
echo "Setting up CNI networking in ${CNI_CONFIG_DIR}..."
4150
mkdir -p "${CNI_CONFIG_DIR}"
4251
cat <<EOF > "${CNI_CONFIG_DIR}/10-containerd-net.conflist"

images/containerd-local-test/entrypoint.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ fi
3030
echo "Initializing container environment..."
3131
setup-containerd.sh
3232

33+
echo "Setting up NRI socket directory..."
34+
mkdir -p /var/run/nri
35+
3336
echo "Starting containerd..."
3437
mkdir -p /run/containerd
3538

@@ -40,14 +43,63 @@ CONTAINERD_PID=$!
4043
# Wait for containerd to be ready
4144
wait-for-containerd.sh
4245

46+
# Trigger the default sandbox creation by listing pods, then capture the mount
47+
# baseline. containerd lazily creates its sandbox shim on the first CRI call,
48+
# so we need to trigger it before baselining.
49+
crictl --runtime-endpoint unix:///run/containerd/containerd.sock pods > /dev/null 2>&1 || true
50+
sleep 1
51+
MOUNT_BASELINE=$(mount | grep -E "containerd.*(shm|rootfs)" || true)
52+
4353
# Execute the command passed to the container
4454
echo "Executing: $@"
4555
"$@"
4656
EXIT_CODE=$?
4757

58+
# Post-test leak detection.
59+
# Leaked sandboxes or orphaned mounts indicate a test bug — every test must
60+
# clean up its sandboxes and containers. If a leak is detected here, the
61+
# test run is marked as failed even if all tests passed, because leaked
62+
# resources cause "Device or resource busy" errors in CI cleanup.
63+
LEAK_DETECTED=0
64+
65+
echo "Checking for leaked sandboxes..."
66+
LEAKED_SANDBOXES=$(crictl --runtime-endpoint unix:///run/containerd/containerd.sock pods 2>/dev/null || true)
67+
SANDBOX_COUNT=0
68+
if echo "$LEAKED_SANDBOXES" | grep -q "POD ID"; then
69+
SANDBOX_COUNT=$(echo "$LEAKED_SANDBOXES" | tail -n +2 | grep -c . || true)
70+
fi
71+
72+
if [ "$SANDBOX_COUNT" -gt 0 ]; then
73+
echo "FAIL: $SANDBOX_COUNT sandbox(es) leaked after tests — test cleanup is broken"
74+
echo "$LEAKED_SANDBOXES"
75+
LEAK_DETECTED=1
76+
else
77+
echo "No leaked sandboxes."
78+
fi
79+
80+
echo "Checking for orphaned mounts..."
81+
CURRENT_MOUNTS=$(mount | grep -E "containerd.*(shm|rootfs)" || true)
82+
# Compare against baseline to find mounts created during the test run.
83+
NEW_MOUNTS=$(diff <(echo "$MOUNT_BASELINE") <(echo "$CURRENT_MOUNTS") | grep "^>" | sed 's/^> //' || true)
84+
if [ -n "$NEW_MOUNTS" ]; then
85+
echo "FAIL: new containerd mounts appeared during tests — sandbox teardown is incomplete"
86+
echo "$NEW_MOUNTS"
87+
echo "Dumping containerd log for mount correlation:"
88+
cat /var/log/containerd.log
89+
LEAK_DETECTED=1
90+
else
91+
echo "No orphaned mounts."
92+
fi
93+
4894
# Cleanup containerd
4995
echo "Cleaning up containerd (PID: $CONTAINERD_PID)..."
5096
kill $CONTAINERD_PID
5197
wait $CONTAINERD_PID || true
5298

99+
# Fail the run if tests passed but resources leaked.
100+
if [ "$EXIT_CODE" -eq 0 ] && [ "$LEAK_DETECTED" -eq 1 ]; then
101+
echo "Tests passed but leaked resources detected — marking run as failed."
102+
exit 1
103+
fi
104+
53105
exit $EXIT_CODE

0 commit comments

Comments
 (0)