mirror of
				https://github.com/NVIDIA/nvidia-container-toolkit
				synced 2025-06-26 18:18:24 +00:00 
			
		
		
		
	Add create-soname-symlinks hook
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	
This change adds a create-soname-symlinks hook that can be used to ensure that the soname symlinks for injected libraries exist in a container. This is done by calling ldconfig -n -N for the folders containing the injected libraries. This also ensures that libcuda.so is present in the ldcache when the update-ldcache hook is run. Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
		
							parent
							
								
									15645e6cd5
								
							
						
					
					
						commit
						bdfaea4e9e
					
				@ -20,6 +20,7 @@ import (
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/chmod"
 | 
			
		||||
	soname "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/create-soname-symlinks"
 | 
			
		||||
	symlinks "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/create-symlinks"
 | 
			
		||||
	"github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/cudacompat"
 | 
			
		||||
	ldcache "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-cdi-hook/update-ldcache"
 | 
			
		||||
@ -34,5 +35,6 @@ func New(logger logger.Interface) []*cli.Command {
 | 
			
		||||
		symlinks.NewCommand(logger),
 | 
			
		||||
		chmod.NewCommand(logger),
 | 
			
		||||
		cudacompat.NewCommand(logger),
 | 
			
		||||
		soname.NewCommand(logger),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										132
									
								
								cmd/nvidia-cdi-hook/create-soname-symlinks/soname-symlinks.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								cmd/nvidia-cdi-hook/create-soname-symlinks/soname-symlinks.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,132 @@
 | 
			
		||||
/**
 | 
			
		||||
# Copyright (c) 2025, NVIDIA CORPORATION.  All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
# You may obtain a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
# Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
# See the License for the specific language governing permissions and
 | 
			
		||||
# limitations under the License.
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
package soname
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
 | 
			
		||||
	"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
 | 
			
		||||
	"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
 | 
			
		||||
	safeexec "github.com/NVIDIA/nvidia-container-toolkit/internal/safe-exec"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type command struct {
 | 
			
		||||
	logger logger.Interface
 | 
			
		||||
	safeexec.Execer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type options struct {
 | 
			
		||||
	folders       cli.StringSlice
 | 
			
		||||
	ldconfigPath  string
 | 
			
		||||
	containerSpec string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCommand constructs an create-soname-symlinks command with the specified logger
 | 
			
		||||
func NewCommand(logger logger.Interface) *cli.Command {
 | 
			
		||||
	c := command{
 | 
			
		||||
		logger: logger,
 | 
			
		||||
		Execer: safeexec.New(logger),
 | 
			
		||||
	}
 | 
			
		||||
	return c.build()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// build the create-soname-symlinks command
 | 
			
		||||
func (m command) build() *cli.Command {
 | 
			
		||||
	cfg := options{}
 | 
			
		||||
 | 
			
		||||
	// Create the 'create-soname-symlinks' command
 | 
			
		||||
	c := cli.Command{
 | 
			
		||||
		Name:  "create-soname-symlinks",
 | 
			
		||||
		Usage: "Create soname symlinks for the specified folders using ldconfig -n -N",
 | 
			
		||||
		Before: func(c *cli.Context) error {
 | 
			
		||||
			return m.validateFlags(c, &cfg)
 | 
			
		||||
		},
 | 
			
		||||
		Action: func(c *cli.Context) error {
 | 
			
		||||
			return m.run(c, &cfg)
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Flags = []cli.Flag{
 | 
			
		||||
		&cli.StringSliceFlag{
 | 
			
		||||
			Name:        "folder",
 | 
			
		||||
			Usage:       "Specify a folder to search for shared libraries for which soname symlinks need to be created",
 | 
			
		||||
			Destination: &cfg.folders,
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "ldconfig-path",
 | 
			
		||||
			Usage:       "Specify the path to the ldconfig program",
 | 
			
		||||
			Destination: &cfg.ldconfigPath,
 | 
			
		||||
			Value:       "/sbin/ldconfig",
 | 
			
		||||
		},
 | 
			
		||||
		&cli.StringFlag{
 | 
			
		||||
			Name:        "container-spec",
 | 
			
		||||
			Usage:       "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN",
 | 
			
		||||
			Destination: &cfg.containerSpec,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m command) validateFlags(c *cli.Context, cfg *options) error {
 | 
			
		||||
	if cfg.ldconfigPath == "" {
 | 
			
		||||
		return errors.New("ldconfig-path must be specified")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m command) run(c *cli.Context, cfg *options) error {
 | 
			
		||||
	s, err := oci.LoadContainerState(cfg.containerSpec)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to load container state: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	containerRoot, err := s.GetContainerRootDirPath()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to determined container root: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if containerRoot == "" {
 | 
			
		||||
		m.logger.Warningf("No container root detected")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dirs := cfg.folders.Value()
 | 
			
		||||
	if len(dirs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ldconfigPath := config.ResolveLDConfigPathOnHost(cfg.ldconfigPath)
 | 
			
		||||
	args := []string{filepath.Base(ldconfigPath)}
 | 
			
		||||
 | 
			
		||||
	args = append(args,
 | 
			
		||||
		// Specify the containerRoot to use.
 | 
			
		||||
		"-r", string(containerRoot),
 | 
			
		||||
		// Specify -n to only process the specified folders.
 | 
			
		||||
		"-n",
 | 
			
		||||
		// Explicitly disable updating the LDCache.
 | 
			
		||||
		"-N",
 | 
			
		||||
	)
 | 
			
		||||
	// Explicitly specific the directories to add.
 | 
			
		||||
	args = append(args, dirs...)
 | 
			
		||||
 | 
			
		||||
	return m.Exec(ldconfigPath, args, nil)
 | 
			
		||||
}
 | 
			
		||||
@ -95,6 +95,13 @@ containerEdits:
 | 
			
		||||
    - create-symlinks
 | 
			
		||||
    - --link
 | 
			
		||||
    - libcuda.so.1::/lib/x86_64-linux-gnu/libcuda.so
 | 
			
		||||
  - hookName: createContainer
 | 
			
		||||
    path: {{ .toolkitRoot }}/nvidia-cdi-hook
 | 
			
		||||
    args:
 | 
			
		||||
    - nvidia-cdi-hook
 | 
			
		||||
    - create-soname-symlinks
 | 
			
		||||
    - --folder
 | 
			
		||||
    - /lib/x86_64-linux-gnu
 | 
			
		||||
  - hookName: createContainer
 | 
			
		||||
    path: {{ .toolkitRoot }}/nvidia-cdi-hook
 | 
			
		||||
    args:
 | 
			
		||||
 | 
			
		||||
@ -97,6 +97,13 @@ containerEdits:
 | 
			
		||||
    - nvidia-cdi-hook
 | 
			
		||||
    - enable-cuda-compat
 | 
			
		||||
    - --host-driver-version=999.88.77
 | 
			
		||||
  - hookName: createContainer
 | 
			
		||||
    path: /usr/bin/nvidia-cdi-hook
 | 
			
		||||
    args:
 | 
			
		||||
    - nvidia-cdi-hook
 | 
			
		||||
    - create-soname-symlinks
 | 
			
		||||
    - --folder
 | 
			
		||||
    - /lib/x86_64-linux-gnu
 | 
			
		||||
  - hookName: createContainer
 | 
			
		||||
    path: /usr/bin/nvidia-cdi-hook
 | 
			
		||||
    args:
 | 
			
		||||
 | 
			
		||||
@ -50,16 +50,16 @@ func (d ldconfig) Hooks() ([]Hook, error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to discover mounts for ldcache update: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	h := CreateLDCacheUpdateHook(
 | 
			
		||||
	hooks := CreateLDCacheUpdateHooks(
 | 
			
		||||
		d.nvidiaCDIHookPath,
 | 
			
		||||
		d.ldconfigPath,
 | 
			
		||||
		getLibraryPaths(mounts),
 | 
			
		||||
	)
 | 
			
		||||
	return []Hook{h}, nil
 | 
			
		||||
	return hooks, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateLDCacheUpdateHook locates the NVIDIA Container Toolkit CLI and creates a hook for updating the LD Cache
 | 
			
		||||
func CreateLDCacheUpdateHook(executable string, ldconfig string, libraries []string) Hook {
 | 
			
		||||
// CreateLDCacheUpdateHooks locates the NVIDIA Container Toolkit CLI and creates a hook for updating the LD Cache
 | 
			
		||||
func CreateLDCacheUpdateHooks(executable string, ldconfig string, libraries []string) []Hook {
 | 
			
		||||
	var args []string
 | 
			
		||||
 | 
			
		||||
	if ldconfig != "" {
 | 
			
		||||
@ -70,13 +70,20 @@ func CreateLDCacheUpdateHook(executable string, ldconfig string, libraries []str
 | 
			
		||||
		args = append(args, "--folder", f)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hook := CreateNvidiaCDIHook(
 | 
			
		||||
		executable,
 | 
			
		||||
		"update-ldcache",
 | 
			
		||||
		args...,
 | 
			
		||||
	)
 | 
			
		||||
	hooks := []Hook{
 | 
			
		||||
		CreateNvidiaCDIHook(
 | 
			
		||||
			executable,
 | 
			
		||||
			"create-soname-symlinks",
 | 
			
		||||
			args...,
 | 
			
		||||
		),
 | 
			
		||||
		CreateNvidiaCDIHook(
 | 
			
		||||
			executable,
 | 
			
		||||
			"update-ldcache",
 | 
			
		||||
			args...,
 | 
			
		||||
		),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return hook
 | 
			
		||||
	return hooks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getLibraryPaths extracts the library dirs from the specified mounts
 | 
			
		||||
 | 
			
		||||
@ -38,11 +38,22 @@ func TestLDCacheUpdateHook(t *testing.T) {
 | 
			
		||||
		mounts        []Mount
 | 
			
		||||
		mountError    error
 | 
			
		||||
		expectedError error
 | 
			
		||||
		expectedArgs  []string
 | 
			
		||||
		expectedHooks []Hook
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			description:  "empty mounts",
 | 
			
		||||
			expectedArgs: []string{"nvidia-cdi-hook", "update-ldcache"},
 | 
			
		||||
			description: "empty mounts",
 | 
			
		||||
			expectedHooks: []Hook{
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "create-soname-symlinks"},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "update-ldcache"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			description:   "mount error",
 | 
			
		||||
@ -65,7 +76,18 @@ func TestLDCacheUpdateHook(t *testing.T) {
 | 
			
		||||
					Path: "/usr/local/lib/libbar.so",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedArgs: []string{"nvidia-cdi-hook", "update-ldcache", "--folder", "/usr/local/lib", "--folder", "/usr/local/libother"},
 | 
			
		||||
			expectedHooks: []Hook{
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "create-soname-symlinks", "--folder", "/usr/local/lib", "--folder", "/usr/local/libother"},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "update-ldcache", "--folder", "/usr/local/lib", "--folder", "/usr/local/libother"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			description: "host paths are ignored",
 | 
			
		||||
@ -75,12 +97,34 @@ func TestLDCacheUpdateHook(t *testing.T) {
 | 
			
		||||
					Path:     "/usr/local/lib/libfoo.so",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedArgs: []string{"nvidia-cdi-hook", "update-ldcache", "--folder", "/usr/local/lib"},
 | 
			
		||||
			expectedHooks: []Hook{
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "create-soname-symlinks", "--folder", "/usr/local/lib"},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "update-ldcache", "--folder", "/usr/local/lib"},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			description:  "explicit ldconfig path is passed",
 | 
			
		||||
			ldconfigPath: testLdconfigPath,
 | 
			
		||||
			expectedArgs: []string{"nvidia-cdi-hook", "update-ldcache", "--ldconfig-path", testLdconfigPath},
 | 
			
		||||
			expectedHooks: []Hook{
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "create-soname-symlinks", "--ldconfig-path", testLdconfigPath},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Lifecycle: "createContainer",
 | 
			
		||||
					Path:      testNvidiaCDIHookPath,
 | 
			
		||||
					Args:      []string{"nvidia-cdi-hook", "update-ldcache", "--ldconfig-path", testLdconfigPath},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -91,12 +135,6 @@ func TestLDCacheUpdateHook(t *testing.T) {
 | 
			
		||||
					return tc.mounts, tc.mountError
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			expectedHook := Hook{
 | 
			
		||||
				Path:      testNvidiaCDIHookPath,
 | 
			
		||||
				Args:      tc.expectedArgs,
 | 
			
		||||
				Lifecycle: "createContainer",
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			d, err := NewLDCacheUpdateHook(logger, mountMock, testNvidiaCDIHookPath, tc.ldconfigPath)
 | 
			
		||||
			require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
@ -110,9 +148,7 @@ func TestLDCacheUpdateHook(t *testing.T) {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			require.NoError(t, err)
 | 
			
		||||
			require.Len(t, hooks, 1)
 | 
			
		||||
 | 
			
		||||
			require.EqualValues(t, hooks[0], expectedHook)
 | 
			
		||||
			require.EqualValues(t, tc.expectedHooks, hooks)
 | 
			
		||||
 | 
			
		||||
			devices, err := d.Devices()
 | 
			
		||||
			require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
@ -215,4 +215,26 @@ var _ = Describe("docker", Ordered, ContinueOnFailure, func() {
 | 
			
		||||
			Expect(ldconfigOut).To(ContainSubstring("/usr/lib64"))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	When("A container is run using CDI", Ordered, func() {
 | 
			
		||||
		BeforeAll(func(ctx context.Context) {
 | 
			
		||||
			_, _, err := r.Run("docker pull ubuntu")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		It("should include libcuda.so in the ldcache", func(ctx context.Context) {
 | 
			
		||||
			ldcacheOutput, _, err := r.Run("docker run --rm -i --runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=runtime.nvidia.com/gpu=all ubuntu bash -c \"ldconfig -p | grep 'libcuda.so'\"")
 | 
			
		||||
			Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
			Expect(ldcacheOutput).ToNot(BeEmpty())
 | 
			
		||||
 | 
			
		||||
			ldcacheLines := strings.Split(ldcacheOutput, "\n")
 | 
			
		||||
			var libs []string
 | 
			
		||||
			for _, line := range ldcacheLines {
 | 
			
		||||
				parts := strings.SplitN(line, " (", 2)
 | 
			
		||||
				libs = append(libs, strings.TrimSpace(parts[0]))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			Expect(libs).To(ContainElements([]string{"libcuda.so", "libcuda.so.1"}))
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user