@@ -19,6 +19,7 @@ package e2e
1919import (
2020 "context"
2121 "fmt"
22+ "path/filepath"
2223
2324 . "github.com/onsi/ginkgo/v2"
2425 . "github.com/onsi/gomega"
@@ -50,41 +51,38 @@ exit 1
5051
5152// containerdTestEnv defines the test environment for different containerd versions
5253type containerdTestEnv struct {
53- name string
54- image string
55- configVersion int
56- pluginPath string
57- hasDefaultImports bool
54+ name string
55+ image string
56+ configVersion int64
57+ pluginPath string
5858}
5959
6060// Define both containerd versions to test
6161var containerdEnvs = []containerdTestEnv {
6262 {
63- name : "containerd-1.7" ,
64- image : "kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e" ,
65- configVersion : 2 ,
66- pluginPath : "io.containerd.grpc.v1.cri" ,
67- hasDefaultImports : false ,
63+ name : "containerd-1.7" ,
64+ image : "kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e" ,
65+ configVersion : 2 ,
66+ pluginPath : "io.containerd.grpc.v1.cri" ,
6867 },
6968 {
70- name : "containerd-2.1" ,
71- image : "docker.io/kindest/base:v20250521-31a79fd4" ,
72- configVersion : 3 ,
73- pluginPath : "io.containerd.cri.v1.runtime" ,
74- hasDefaultImports : true ,
69+ name : "containerd-2.1" ,
70+ image : "docker.io/kindest/base:v20250521-31a79fd4" ,
71+ configVersion : 3 ,
72+ pluginPath : "io.containerd.cri.v1.runtime" ,
7573 },
7674}
7775
7876// Integration tests for containerd drop-in config functionality
7977var _ = Describe ("containerd" , Ordered , ContinueOnFailure , Label ("container-runtime" ), func () {
8078 // Run all tests for each containerd version
8179 for _ , env := range containerdEnvs {
82- env := env // capture loop variable
83-
84- Context (fmt .Sprintf ("with %s" , env .name ), Ordered , func () {
80+ Context (env .name , Ordered , func () {
8581 var (
86- nestedContainerRunner Runner
87- containerName = fmt .Sprintf ("nvctk-e2e-containerd-%s-tests" , env .name )
82+ nestedContainerRunner Runner
83+ containerName = "nvctk-e2e-containerd-tests-" + env .name
84+ originalTopLevelConfigContents string
85+ // originalTopLevelConfigToml *toml.Tree
8886 )
8987
9088 // ensureContainerdRunning starts containerd if not running and waits for it to be ready
@@ -124,7 +122,15 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
124122 var err error
125123
126124 // Create the nested container with the global cache mounted
127- nestedContainerRunner , err = NewNestedContainerRunner (runner , env .image , installCTK , containerName , localCacheDir )
125+ // TODO: This runner doesn't actually NEED GPU access.
126+ nestedContainerRunner , err = NewNestedContainerRunner (runner , env .image , false , containerName , localCacheDir )
127+ Expect (err ).ToNot (HaveOccurred ())
128+
129+ // Store the contents of the original config.
130+ originalTopLevelConfigContents , _ , err = nestedContainerRunner .Run ("cat /etc/containerd/config.toml" )
131+ Expect (err ).ToNot (HaveOccurred ())
132+
133+ _ , err = toml .Load (originalTopLevelConfigContents )
128134 Expect (err ).ToNot (HaveOccurred ())
129135
130136 // Backup original containerd configuration
@@ -133,7 +139,7 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
133139 if [ -d /etc/containerd/conf.d ]; then
134140 cp -r /etc/containerd/conf.d /tmp/containerd-conf.d.backup
135141 fi
136-
142+
137143 # Backup the original config.toml
138144 if [ -f /etc/containerd/config.toml ]; then
139145 cp /etc/containerd/config.toml /tmp/containerd-config.toml.backup
@@ -174,7 +180,7 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
174180 rm -rf /etc/containerd/conf.d
175181 mkdir -p /etc/containerd/conf.d
176182 fi
177-
183+
178184 # Restore the original config.toml
179185 if [ -f /tmp/containerd-config.toml.backup ]; then
180186 cp /tmp/containerd-config.toml.backup /etc/containerd/config.toml
@@ -196,12 +202,16 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
196202 _ , _ , err := nestedContainerRunner .Run (`nvidia-ctk runtime configure --runtime=containerd --config=/etc/containerd/config.toml --drop-in-config=/etc/containerd/conf.d/99-nvidia.toml --set-as-default --cdi.enabled` )
197203 Expect (err ).ToNot (HaveOccurred (), "Failed to configure containerd" )
198204
199- // For containerd 1.7, verify nvidia-ctk added imports directive to main config
200- if ! env .hasDefaultImports {
201- output , _ , err := nestedContainerRunner .Run (`grep "^imports" /etc/containerd/config.toml` )
202- Expect (err ).ToNot (HaveOccurred (), "nvidia-ctk should have added imports directive to main config" )
203- Expect (output ).To (ContainSubstring (`imports = ["/etc/containerd/conf.d/*.toml"]` ))
204- }
205+ topLevelConfigContents , _ , err := nestedContainerRunner .Run ("cat /etc/containerd/config.toml" )
206+ Expect (err ).ToNot (HaveOccurred ())
207+
208+ _ , err = toml .Load (topLevelConfigContents )
209+ Expect (err ).ToNot (HaveOccurred ())
210+
211+ dropInConfigContents , _ , err := nestedContainerRunner .Run ("cat /etc/containerd/conf.d/99-nvidia.toml" )
212+ Expect (err ).ToNot (HaveOccurred ())
213+
214+ Expect (dropInConfigContents ).ToNot (BeEmpty ())
205215
206216 // restart containerd
207217 err = restartContainerdAndWait (nestedContainerRunner )
@@ -212,53 +222,64 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
212222 Expect (err ).ToNot (HaveOccurred ())
213223
214224 // Parse the TOML output
215- config , err := parseContainerdConfig (output )
225+ config , err := toml . Load (output )
216226 Expect (err ).ToNot (HaveOccurred (), "Failed to parse containerd config" )
217227
218- // Verify config version
219- version := config .Get ("version" )
220- Expect (version ).To (BeNumerically ("==" , env .configVersion ))
221-
222- // Verify imports
223- // Note: containerd config dump behavior differs between versions:
224- // - containerd 1.7: Shows resolved file paths from glob patterns
225- // - containerd 2.x: May show the glob pattern or omit imports entirely
226- if env .configVersion == 2 {
227- // containerd 1.7 shows actual resolved imports
228- err = validateImports (config , []string {"/etc/containerd/conf.d/99-nvidia.toml" }, true )
229- Expect (err ).ToNot (HaveOccurred (), "Import validation failed" )
230- }
231- // For containerd 2.x, imports validation is skipped as the behavior is inconsistent
232-
233- // Get plugin configuration
234- pluginConfig , err := getPluginConfig (config , env .configVersion )
235- Expect (err ).ToNot (HaveOccurred (), "Failed to get plugin config" )
236-
237- // Verify CDI is enabled
238- cdiEnabled , err := getCDIEnabled (pluginConfig )
239- Expect (err ).ToNot (HaveOccurred (), "Failed to get CDI config" )
240- Expect (cdiEnabled ).To (BeTrue (), "CDI should be enabled" )
241-
242- // Verify default runtime
243- defaultRuntime , err := getDefaultRuntime (pluginConfig )
244- Expect (err ).ToNot (HaveOccurred (), "Failed to get default runtime" )
245- Expect (defaultRuntime ).To (Equal ("nvidia" ), "Default runtime should be nvidia" )
246-
247- // Get runtimes configuration
248- runtimes , err := getRuntimesConfig (pluginConfig )
249- Expect (err ).ToNot (HaveOccurred (), "Failed to get runtimes config" )
250-
251- // Verify NVIDIA runtime exists and is properly configured
252- nvidiaRuntime , exists := runtimes ["nvidia" ]
253- Expect (exists ).To (BeTrue (), "nvidia runtime should exist" )
254-
255- // Validate nvidia runtime configuration
256- expectedOptions := map [string ]interface {}{
257- "BinaryName" : "/usr/bin/nvidia-container-runtime" ,
258- "SystemdCgroup" : true ,
228+ BeAValidConfig := func (env * containerdTestEnv ) types.GomegaMatcher {
229+ return And (
230+ WithTransform (
231+ func (c * toml.Tree ) map [string ]any {
232+ return c .ToMap ()
233+ },
234+ And (
235+ HaveKeyWithValue ("version" , env .configVersion ),
236+ HaveKeyWithValue ("imports" , WithTransform (func (is []any ) []string {
237+ var basePaths []string
238+ for _ , i := range is {
239+ basePaths = append (basePaths , filepath .Dir (i .(string )))
240+ }
241+ return basePaths
242+ },
243+ // TODO: We could do better at matching the following:
244+ // /etc/containerd/conf.d/99-nvidia.toml
245+ // /etc/containerd/conf.d/*.toml
246+ ContainElements ("/etc/containerd/conf.d" ),
247+ )),
248+ ),
249+ ),
250+ WithTransform (
251+ // Get the plugins config.
252+ func (c * toml.Tree ) map [string ]any {
253+ pt := c .GetPath ([]string {"plugins" , env .pluginPath })
254+ if pt != nil {
255+ return pt .(* toml.Tree ).ToMap ()
256+ }
257+ return nil
258+ },
259+ And (
260+ HaveKeyWithValue ("enable_cdi" , true ),
261+ HaveKeyWithValue ("containerd" ,
262+ And (
263+ // TODO: This should depend on whether we set the default.
264+ HaveKeyWithValue ("default_runtime_name" , "nvidia" ),
265+ HaveKeyWithValue ("runtimes" , HaveKeyWithValue ("nvidia" ,
266+ And (
267+ HaveKeyWithValue ("runtime_type" , "io.containerd.runc.v2" ),
268+ HaveKeyWithValue ("options" ,
269+ And (
270+ HaveKeyWithValue ("BinaryName" , "/usr/bin/nvidia-container-runtime" ),
271+ HaveKeyWithValue ("SystemdCgroup" , true ),
272+ ),
273+ ),
274+ ),
275+ )),
276+ ),
277+ ),
278+ ),
279+ ),
280+ )
259281 }
260- err = validateRuntimeConfig (nvidiaRuntime , "" , expectedOptions )
261- Expect (err ).ToNot (HaveOccurred (), "NVIDIA runtime validation failed" )
282+ Expect (config ).To (BeAValidConfig (& env ))
262283 })
263284 })
264285
@@ -275,14 +296,14 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
275296 [plugins."io.containerd.grpc.v1.cri"]
276297 [plugins."io.containerd.grpc.v1.cri".containerd]
277298 default_runtime_name = "kata"
278-
299+
279300 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
280301 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
281302 runtime_type = "io.containerd.runc.v2"
282-
303+
283304 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
284305 runtime_type = "io.containerd.kata.v2"
285-
306+
286307 [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata.options]
287308 ConfigPath = "/etc/kata-containers/configuration.toml"`
288309 } else {
@@ -293,14 +314,14 @@ var _ = Describe("containerd", Ordered, ContinueOnFailure, Label("container-runt
293314 [plugins."io.containerd.cri.v1.runtime"]
294315 [plugins."io.containerd.cri.v1.runtime".containerd]
295316 default_runtime_name = "kata"
296-
317+
297318 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
298319 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
299320 runtime_type = "io.containerd.runc.v2"
300-
321+
301322 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.kata]
302323 runtime_type = "io.containerd.kata.v2"
303-
324+
304325 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.kata.options]
305326 ConfigPath = "/etc/kata-containers/configuration.toml"`
306327 }
@@ -382,11 +403,11 @@ version = 3
382403 [plugins."io.containerd.cri.v1.runtime"]
383404 [plugins."io.containerd.cri.v1.runtime".containerd]
384405 default_runtime_name = "runc"
385-
406+
386407 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
387408 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
388409 runtime_type = "io.containerd.runc.v2"
389-
410+
390411 [plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options]
391412 BinaryName = "/usr/bin/runc"
392413 SystemdCgroup = true
@@ -577,7 +598,7 @@ func tomlTreeToMap(tree *toml.Tree) map[string]interface{} {
577598}
578599
579600// getPluginConfig navigates to the appropriate plugin configuration based on containerd version
580- func getPluginConfig (tree * toml.Tree , version int ) (* toml.Tree , error ) {
601+ func getPluginConfig (tree * toml.Tree , version int64 ) (* toml.Tree , error ) {
581602 var pluginPath []string
582603 if version == 2 {
583604 pluginPath = []string {"plugins" , "io.containerd.grpc.v1.cri" }
@@ -602,8 +623,18 @@ func getPluginConfig(tree *toml.Tree, version int) (*toml.Tree, error) {
602623}
603624
604625// getRuntimesConfig gets the runtimes configuration from the plugin config
605- func getRuntimesConfig (pluginConfig * toml.Tree ) (map [string ]interface {}, error ) {
606- runtimes := pluginConfig .GetPath ([]string {"containerd" , "runtimes" })
626+ func getRuntimesConfig (pluginConfig * toml.Tree , _ int64 ) (map [string ]interface {}, error ) {
627+ containerdSection := pluginConfig .Get ("containerd" )
628+ if containerdSection == nil {
629+ return nil , fmt .Errorf ("containerd section not found" )
630+ }
631+
632+ containerdTree , ok := containerdSection .(* toml.Tree )
633+ if ! ok {
634+ return nil , fmt .Errorf ("containerd section is not a TOML tree" )
635+ }
636+
637+ runtimes := containerdTree .Get ("runtimes" )
607638 if runtimes == nil {
608639 return nil , fmt .Errorf ("runtimes section not found" )
609640 }
0 commit comments