Compare commits

...

15 Commits

Author SHA1 Message Date
Evan Lezar
d78868cd31 Merge pull request #760 from elezar/bump-release-v1.17.0-rc.2
Some checks failed
CodeQL / Analyze Go code with CodeQL (push) Has been cancelled
Golang / check (push) Has been cancelled
Golang / Unit test (push) Has been cancelled
Golang / Build (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, centos7-aarch64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, centos7-x86_64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, centos8-ppc64le) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, ubuntu18.04-amd64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, ubuntu18.04-arm64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, ubuntu18.04-ppc64le) (push) Has been cancelled
image / image (packaging, ${{github.event_name == 'pull_request'}}) (push) Has been cancelled
image / image (ubi8, ${{github.event_name == 'pull_request'}}) (push) Has been cancelled
image / image (ubuntu20.04, ${{github.event_name == 'pull_request'}}) (push) Has been cancelled
Bump version for v1.17.0-rc.2 release
2024-10-28 14:26:53 +01:00
Evan Lezar
74b1e5ea8c Bump version for v1.17.0-rc.2 release
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-28 14:09:12 +01:00
Evan Lezar
88608781b6 Merge pull request #755 from elezar/fix-libcuda-so
Some checks failed
CodeQL / Analyze Go code with CodeQL (push) Has been cancelled
Golang / check (push) Has been cancelled
Golang / Unit test (push) Has been cancelled
Golang / Build (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, centos7-aarch64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, centos7-x86_64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, centos8-ppc64le) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, ubuntu18.04-amd64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, ubuntu18.04-arm64) (push) Has been cancelled
image / packages (${{github.event_name == 'pull_request'}}, ubuntu18.04-ppc64le) (push) Has been cancelled
image / image (packaging, ${{github.event_name == 'pull_request'}}) (push) Has been cancelled
image / image (ubi8, ${{github.event_name == 'pull_request'}}) (push) Has been cancelled
image / image (ubuntu20.04, ${{github.event_name == 'pull_request'}}) (push) Has been cancelled
Fix bug where libcuda.so is not found in ldcache
2024-10-24 23:33:12 +02:00
Evan Lezar
fa5a4ac499 Read ldcache at construction instead of on each locate call
This change udpates the ldcache locator to read the ldcache at construction
and use these contents to perform future lookups against. Each of the cache
entries are resolved and lookups return the resolved target.

Assuming a symlink chain: libcuda.so -> libcuda.so.1 -> libcuda.so.VERSION, this
means that libcuda.so.VERION will be returned for any of the following inputs:
libcuda.so, libcuda.so.1, libcudal.so.*.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-24 23:12:58 +02:00
Evan Lezar
9f1bd62c42 [no-relnote] Add failing libcuda locate test
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-24 15:56:53 +02:00
Evan Lezar
9534249936 [no-relnote] Add test for libcuda lookup
This change adds a test for locating libcuda as a driver library.
This includes a failing test on a system where libcuda.so.1 is in
the ldcache, but not at one of the predefined library search paths.

A testdata folder with sample root filesystems is included to test
various combinations.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-24 15:56:25 +02:00
Evan Lezar
e1ea0056b9 Fix bug in sorting of symlink chain
Since we use a map to keep track of the elements of a symlink chain
the construction of the final list of located elements is not stable.
This change constructs the output as this is being discovered and as
such maintains the original ordering.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-24 15:56:25 +02:00
Evan Lezar
c802c3089c Remove unsupported print-ldcache command
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-24 15:56:25 +02:00
Tariq
771ac6b88a Merge pull request #756 from NVIDIA/cli-source-fallback
[TOML ConfigSource] add support for executing fallback CLI commands
2024-10-23 14:31:45 -07:00
Tariq Ibrahim
0f7aba9c3c [TOML ConfigSource] add support for executing fallback CLI commands
Signed-off-by: Tariq Ibrahim <tibrahim@nvidia.com>
Co-authored-by: Evan Lezar <elezar@nvidia.com>
2024-10-23 14:26:17 -07:00
Tariq
3c07ea0b17 Merge pull request #726 from NVIDIA/dependabot/docker/deployments/devel/main/golang-1.23.2
Bump golang from 1.23.1 to 1.23.2 in /deployments/devel
2024-10-21 10:11:21 -07:00
Evan Lezar
183dff9161 Merge pull request #750 from elezar/remove-csv-filenames-support
Remove csv filenames support
2024-10-21 11:10:27 +02:00
Evan Lezar
5e3e91a010 [no-relnote] Minor cleanup in create-symlinks
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-18 16:27:38 +02:00
Evan Lezar
dc0e191093 Remove csv-filename support from create-symlinks
This change removes support for specifying csv-filenames when
calling the create-symlinks hook. This is no longer required
as tegra-based systems generate hooks with `--link` arguments.

This also allows the hook to better serve as a reference implementation
for upstream projects wanting to implement a set of standard CDI hooks.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2024-10-18 16:27:27 +02:00
dependabot[bot]
a9ca6995f7 Bump golang from 1.23.1 to 1.23.2 in /deployments/devel
Bumps golang from 1.23.1 to 1.23.2.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-02 08:37:23 +00:00
31 changed files with 380 additions and 454 deletions

View File

@@ -1,5 +1,14 @@
# NVIDIA Container Toolkit Changelog
## v1.17.0-rc.2
- Fix bug in locating libcuda.so form ldcache
- Fix bug in sorting of symlink chain
- Remove unsupported print-ldcache command
- Remove csv-filename support from create-symlinks
## Changes in the Toolkit Container
- Fallback to `crio-status` if `crio status` does not work when configuring the crio runtime
## v1.17.0-rc.1
- Allow IMEX channels to be requested as volume mounts
- Fix typo in error message

View File

@@ -25,10 +25,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
"github.com/NVIDIA/nvidia-container-toolkit/internal/oci"
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
)
type command struct {
@@ -37,7 +34,6 @@ type command struct {
type config struct {
hostRoot string
filenames cli.StringSlice
links cli.StringSlice
containerSpec string
}
@@ -50,39 +46,36 @@ func NewCommand(logger logger.Interface) *cli.Command {
return c.build()
}
// build
// build creates the create-symlink command.
func (m command) build() *cli.Command {
cfg := config{}
// Create the '' command
c := cli.Command{
Name: "create-symlinks",
Usage: "A hook to create symlinks in the container. This can be used to process CSV mount specs",
Usage: "A hook to create symlinks in the container.",
Action: func(c *cli.Context) error {
return m.run(c, &cfg)
},
}
c.Flags = []cli.Flag{
&cli.StringFlag{
Name: "host-root",
Usage: "The root on the host filesystem to use to resolve symlinks",
Destination: &cfg.hostRoot,
},
&cli.StringSliceFlag{
Name: "csv-filename",
Usage: "Specify a (CSV) filename to process",
Destination: &cfg.filenames,
},
&cli.StringSliceFlag{
Name: "link",
Usage: "Specify a specific link to create. The link is specified as target::link",
Destination: &cfg.links,
},
// The following flags are testing-only flags.
&cli.StringFlag{
Name: "host-root",
Usage: "The root on the host filesystem to use to resolve symlinks. This is only intended for testing.",
Destination: &cfg.hostRoot,
Hidden: true,
},
&cli.StringFlag{
Name: "container-spec",
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN",
Usage: "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN. This is only intended for testing.",
Destination: &cfg.containerSpec,
Hidden: true,
},
}
@@ -100,53 +93,8 @@ func (m command) run(c *cli.Context, cfg *config) error {
return fmt.Errorf("failed to determined container root: %v", err)
}
csvFiles := cfg.filenames.Value()
chainLocator := lookup.NewSymlinkChainLocator(
lookup.WithLogger(m.logger),
lookup.WithRoot(cfg.hostRoot),
)
var candidates []string
for _, file := range csvFiles {
mountSpecs, err := csv.NewCSVFileParser(m.logger, file).Parse()
if err != nil {
m.logger.Debugf("Skipping CSV file %v: %v", file, err)
continue
}
for _, ms := range mountSpecs {
if ms.Type != csv.MountSpecSym {
continue
}
targets, err := chainLocator.Locate(ms.Path)
if err != nil {
m.logger.Warningf("Failed to locate symlink %v", ms.Path)
}
candidates = append(candidates, targets...)
}
}
created := make(map[string]bool)
// candidates is a list of absolute paths to symlinks in a chain, or the final target of the chain.
for _, candidate := range candidates {
target, err := symlinks.Resolve(candidate)
if err != nil {
m.logger.Debugf("Skipping invalid link: %v", err)
continue
} else if target == candidate {
m.logger.Debugf("%v is not a symlink", candidate)
continue
}
err = m.createLink(created, cfg.hostRoot, containerRoot, target, candidate)
if err != nil {
m.logger.Warningf("Failed to create link %v: %v", []string{target, candidate}, err)
}
}
links := cfg.links.Value()
for _, l := range links {
for _, l := range cfg.links.Value() {
parts := strings.Split(l, "::")
if len(parts) != 2 {
m.logger.Warningf("Invalid link specification %v", l)
@@ -158,9 +106,7 @@ func (m command) run(c *cli.Context, cfg *config) error {
m.logger.Warningf("Failed to create link %v: %v", parts, err)
}
}
return nil
}
func (m command) createLink(created map[string]bool, hostRoot string, containerRoot string, target string, link string) error {
@@ -207,18 +153,3 @@ func changeRoot(current string, new string, path string) (string, error) {
return filepath.Join(new, relative), nil
}
// Locate returns the link target of the specified filename or an empty slice if the
// specified filename is not a symlink.
func (m command) Locate(filename string) ([]string, error) {
target, err := symlinks.Resolve(filename)
if err != nil {
return nil, err
}
if target == filename {
m.logger.Debugf("%v is not a symlink", filename)
return nil, nil
}
m.logger.Debugf("Resolved link: '%v' => '%v'", filename, target)
return []string{target}, nil
}

View File

@@ -1,102 +0,0 @@
/**
# Copyright (c) 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 createdevicenodes
import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
)
type command struct {
logger logger.Interface
}
type options struct {
driverRoot string
}
// NewCommand constructs a command sub-command with the specified logger
func NewCommand(logger logger.Interface) *cli.Command {
c := command{
logger: logger,
}
return c.build()
}
// build
func (m command) build() *cli.Command {
opts := options{}
c := cli.Command{
Name: "print-ldcache",
Usage: "A utility to print the contents of the ldcache",
Before: func(c *cli.Context) error {
return m.validateFlags(c, &opts)
},
Action: func(c *cli.Context) error {
return m.run(c, &opts)
},
}
c.Flags = []cli.Flag{
&cli.StringFlag{
Name: "driver-root",
Usage: "the path to the driver root. Device nodes will be created at `DRIVER_ROOT`/dev",
Value: "/",
Destination: &opts.driverRoot,
EnvVars: []string{"NVIDIA_DRIVER_ROOT", "DRIVER_ROOT"},
},
}
return &c
}
func (m command) validateFlags(r *cli.Context, opts *options) error {
return nil
}
func (m command) run(c *cli.Context, opts *options) error {
cache, err := ldcache.New(m.logger, opts.driverRoot)
if err != nil {
return fmt.Errorf("failed to create ldcache: %v", err)
}
lib32, lib64 := cache.List()
if len(lib32) == 0 {
m.logger.Info("No 32-bit libraries found")
} else {
m.logger.Infof("%d 32-bit libraries found", len(lib32))
for _, lib := range lib32 {
m.logger.Infof("%v", lib)
}
}
if len(lib64) == 0 {
m.logger.Info("No 64-bit libraries found")
} else {
m.logger.Infof("%d 64-bit libraries found", len(lib64))
for _, lib := range lib64 {
m.logger.Infof("%v", lib)
}
}
return nil
}

View File

@@ -21,7 +21,6 @@ import (
devchar "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/system/create-dev-char-symlinks"
devicenodes "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/system/create-device-nodes"
ldcache "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/system/print-ldcache"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
)
@@ -47,7 +46,6 @@ func (m command) build() *cli.Command {
system.Subcommands = []*cli.Command{
devchar.NewCommand(m.logger),
devicenodes.NewCommand(m.logger),
ldcache.NewCommand(m.logger),
}
return &system

View File

@@ -14,7 +14,7 @@
# This Dockerfile is also used to define the golang version used in this project
# This allows dependabot to manage this version in addition to other images.
FROM golang:1.23.1
FROM golang:1.23.2
WORKDIR /work
COPY * .

View File

@@ -22,15 +22,12 @@ import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"unsafe"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
)
const ldcachePath = "/etc/ld.so.cache"
@@ -82,10 +79,9 @@ type entry2 struct {
// LDCache represents the interface for performing lookups into the LDCache
//
//go:generate moq -out ldcache_mock.go . LDCache
//go:generate moq -rm -out ldcache_mock.go . LDCache
type LDCache interface {
List() ([]string, []string)
Lookup(...string) ([]string, []string)
}
type ldcache struct {
@@ -105,14 +101,7 @@ func New(logger logger.Interface, root string) (LDCache, error) {
logger.Debugf("Opening ld.conf at %v", path)
f, err := os.Open(path)
if os.IsNotExist(err) {
logger.Warningf("Could not find ld.so.cache at %v; creating empty cache", path)
e := &empty{
logger: logger,
path: path,
}
return e, nil
} else if err != nil {
if err != nil {
return nil, err
}
defer f.Close()
@@ -196,7 +185,7 @@ type entry struct {
}
// getEntries returns the entires of the ldcache in a go-friendly struct.
func (c *ldcache) getEntries(selected func(string) bool) []entry {
func (c *ldcache) getEntries() []entry {
var entries []entry
for _, e := range c.entries {
bits := 0
@@ -223,9 +212,6 @@ func (c *ldcache) getEntries(selected func(string) bool) []entry {
c.logger.Debugf("Skipping invalid lib")
continue
}
if !selected(lib) {
continue
}
value := bytesToString(c.libs[e.Value:])
if value == "" {
c.logger.Debugf("Skipping invalid value for lib %v", lib)
@@ -236,51 +222,19 @@ func (c *ldcache) getEntries(selected func(string) bool) []entry {
bits: bits,
value: value,
}
entries = append(entries, e)
}
return entries
}
// List creates a list of libraries in the ldcache.
// The 32-bit and 64-bit libraries are returned separately.
func (c *ldcache) List() ([]string, []string) {
all := func(s string) bool { return true }
return c.resolveSelected(all)
}
// Lookup searches the ldcache for the specified prefixes.
// The 32-bit and 64-bit libraries matching the prefixes are returned.
func (c *ldcache) Lookup(libPrefixes ...string) ([]string, []string) {
c.logger.Debugf("Looking up %v in cache", libPrefixes)
// We define a functor to check whether a given library name matches any of the prefixes
matchesAnyPrefix := func(s string) bool {
for _, p := range libPrefixes {
if strings.HasPrefix(s, p) {
return true
}
}
return false
}
return c.resolveSelected(matchesAnyPrefix)
}
// resolveSelected process the entries in the LDCach based on the supplied filter and returns the resolved paths.
// The paths are separated by bittage.
func (c *ldcache) resolveSelected(selected func(string) bool) ([]string, []string) {
paths := make(map[int][]string)
processed := make(map[string]bool)
for _, e := range c.getEntries(selected) {
path, err := c.resolve(e.value)
if err != nil {
c.logger.Debugf("Could not resolve entry: %v", err)
continue
}
for _, e := range c.getEntries() {
path := filepath.Join(c.root, e.value)
if processed[path] {
continue
}
@@ -291,29 +245,6 @@ func (c *ldcache) resolveSelected(selected func(string) bool) ([]string, []strin
return paths[32], paths[64]
}
// resolve resolves the specified ldcache entry based on the value being processed.
// The input is the name of the entry in the cache.
func (c *ldcache) resolve(target string) (string, error) {
name := filepath.Join(c.root, target)
c.logger.Debugf("checking %v", name)
link, err := symlinks.Resolve(name)
if err != nil {
return "", fmt.Errorf("failed to resolve symlink: %v", err)
}
if link == name {
return name, nil
}
// We return absolute paths for all targets
if !filepath.IsAbs(link) || strings.HasPrefix(link, ".") {
link = filepath.Join(filepath.Dir(target), link)
}
return c.resolve(link)
}
// bytesToString converts a byte slice to a string.
// This assumes that the byte slice is null-terminated
func bytesToString(value []byte) string {

View File

@@ -20,9 +20,6 @@ var _ LDCache = &LDCacheMock{}
// ListFunc: func() ([]string, []string) {
// panic("mock out the List method")
// },
// LookupFunc: func(strings ...string) ([]string, []string) {
// panic("mock out the Lookup method")
// },
// }
//
// // use mockedLDCache in code that requires LDCache
@@ -33,22 +30,13 @@ type LDCacheMock struct {
// ListFunc mocks the List method.
ListFunc func() ([]string, []string)
// LookupFunc mocks the Lookup method.
LookupFunc func(strings ...string) ([]string, []string)
// calls tracks calls to the methods.
calls struct {
// List holds details about calls to the List method.
List []struct {
}
// Lookup holds details about calls to the Lookup method.
Lookup []struct {
// Strings is the strings argument value.
Strings []string
}
}
lockList sync.RWMutex
lockLookup sync.RWMutex
lockList sync.RWMutex
}
// List calls ListFunc.
@@ -77,35 +65,3 @@ func (mock *LDCacheMock) ListCalls() []struct {
mock.lockList.RUnlock()
return calls
}
// Lookup calls LookupFunc.
func (mock *LDCacheMock) Lookup(strings ...string) ([]string, []string) {
if mock.LookupFunc == nil {
panic("LDCacheMock.LookupFunc: method is nil but LDCache.Lookup was just called")
}
callInfo := struct {
Strings []string
}{
Strings: strings,
}
mock.lockLookup.Lock()
mock.calls.Lookup = append(mock.calls.Lookup, callInfo)
mock.lockLookup.Unlock()
return mock.LookupFunc(strings...)
}
// LookupCalls gets all the calls that were made to Lookup.
// Check the length with:
//
// len(mockedLDCache.LookupCalls())
func (mock *LDCacheMock) LookupCalls() []struct {
Strings []string
} {
var calls []struct {
Strings []string
}
mock.lockLookup.RLock()
calls = mock.calls.Lookup
mock.lockLookup.RUnlock()
return calls
}

118
internal/lookup/ldcache.go Normal file
View File

@@ -0,0 +1,118 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 lookup
import (
"fmt"
"path/filepath"
"slices"
"github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache"
)
type ldcacheLocator struct {
*builder
resolvesTo map[string]string
}
var _ Locator = (*ldcacheLocator)(nil)
func NewLdcacheLocator(opts ...Option) Locator {
b := newBuilder(opts...)
cache, err := ldcache.New(b.logger, b.root)
if err != nil {
b.logger.Warningf("Failed to load ldcache: %v", err)
if b.isOptional {
return &null{}
}
return &notFound{}
}
chain := NewSymlinkChainLocator(WithOptional(true))
resolvesTo := make(map[string]string)
_, libs64 := cache.List()
for _, library := range libs64 {
if _, processed := resolvesTo[library]; processed {
continue
}
candidates, err := chain.Locate(library)
if err != nil {
b.logger.Errorf("error processing library %s from ldcache: %v", library, err)
continue
}
if len(candidates) == 0 {
resolvesTo[library] = library
continue
}
// candidates represents a symlink chain.
// The first element represents the start of the chain and the last
// element the final target.
target := candidates[len(candidates)-1]
for _, candidate := range candidates {
resolvesTo[candidate] = target
}
}
return &ldcacheLocator{
builder: b,
resolvesTo: resolvesTo,
}
}
// Locate finds the specified libraryname.
// If the input is a library name, the ldcache is searched otherwise the
// provided path is resolved as a symlink.
func (l ldcacheLocator) Locate(libname string) ([]string, error) {
var matcher func(string, string) bool
if filepath.IsAbs(libname) {
matcher = func(p string, c string) bool {
m, _ := filepath.Match(filepath.Join(l.root, p), c)
return m
}
} else {
matcher = func(p string, c string) bool {
m, _ := filepath.Match(p, filepath.Base(c))
return m
}
}
var matches []string
seen := make(map[string]bool)
for name, target := range l.resolvesTo {
if !matcher(libname, name) {
continue
}
if seen[target] {
continue
}
seen[target] = true
matches = append(matches, target)
}
slices.Sort(matches)
if len(matches) == 0 && !l.isOptional {
return nil, fmt.Errorf("%s: %w", libname, ErrNotFound)
}
return matches, nil
}

View File

@@ -0,0 +1,77 @@
package lookup
import (
"path/filepath"
"testing"
testlog "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/require"
"github.com/NVIDIA/nvidia-container-toolkit/internal/test"
)
func TestLDCacheLookup(t *testing.T) {
logger, _ := testlog.NewNullLogger()
moduleRoot, err := test.GetModuleRoot()
require.NoError(t, err)
testCases := []struct {
rootFs string
inputs []string
expected string
expectedError error
}{
{
rootFs: "rootfs-empty",
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
expectedError: ErrNotFound,
},
{
rootFs: "rootfs-1",
inputs: []string{
"libcuda.so.1",
"libcuda.so.*",
"libcuda.so.*.*",
"libcuda.so.999.88.77",
"/lib/x86_64-linux-gnu/libcuda.so.1",
"/lib/x86_64-linux-gnu/libcuda.so.*",
"/lib/x86_64-linux-gnu/libcuda.so.*.*",
"/lib/x86_64-linux-gnu/libcuda.so.999.88.77",
},
expected: "/lib/x86_64-linux-gnu/libcuda.so.999.88.77",
},
{
rootFs: "rootfs-2",
inputs: []string{
"libcuda.so.1",
"libcuda.so.*",
"libcuda.so.*.*",
"libcuda.so.999.88.77",
"/var/lib/nvidia/lib64/libcuda.so.1",
"/var/lib/nvidia/lib64/libcuda.so.*",
"/var/lib/nvidia/lib64/libcuda.so.*.*",
"/var/lib/nvidia/lib64/libcuda.so.999.88.77",
},
expected: "/var/lib/nvidia/lib64/libcuda.so.999.88.77",
},
}
for _, tc := range testCases {
for _, input := range tc.inputs {
t.Run(tc.rootFs+" "+input, func(t *testing.T) {
rootfs := filepath.Join(moduleRoot, "testdata", "lookup", tc.rootFs)
l := NewLdcacheLocator(
WithLogger(logger),
WithRoot(rootfs),
)
candidates, err := l.Locate(input)
require.ErrorIs(t, err, tc.expectedError)
if tc.expectedError == nil {
require.Equal(t, []string{filepath.Join(rootfs, tc.expected)}, candidates)
}
})
}
}
}

View File

@@ -16,20 +16,6 @@
package lookup
import (
"fmt"
"github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
)
type ldcacheLocator struct {
logger logger.Interface
cache ldcache.LDCache
}
var _ Locator = (*ldcacheLocator)(nil)
// NewLibraryLocator creates a library locator using the specified options.
func NewLibraryLocator(opts ...Option) Locator {
b := newBuilder(opts...)
@@ -63,39 +49,7 @@ func NewLibraryLocator(opts ...Option) Locator {
l := First(
symlinkLocator,
newLdcacheLocator(opts...),
NewLdcacheLocator(opts...),
)
return l
}
func newLdcacheLocator(opts ...Option) Locator {
b := newBuilder(opts...)
cache, err := ldcache.New(b.logger, b.root)
if err != nil {
// If we failed to open the LDCache, we default to a symlink locator.
b.logger.Warningf("Failed to load ldcache: %v", err)
return nil
}
return &ldcacheLocator{
logger: b.logger,
cache: cache,
}
}
// Locate finds the specified libraryname.
// If the input is a library name, the ldcache is searched otherwise the
// provided path is resolved as a symlink.
func (l ldcacheLocator) Locate(libname string) ([]string, error) {
paths32, paths64 := l.cache.Lookup(libname)
if len(paths32) > 0 {
l.logger.Warningf("Ignoring 32-bit libraries for %v: %v", libname, paths32)
}
if len(paths64) == 0 {
return nil, fmt.Errorf("64-bit library %v: %w", libname, ErrNotFound)
}
return paths64, nil
}

View File

@@ -24,82 +24,8 @@ import (
testlog "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/require"
"github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache"
)
func TestLDCacheLocator(t *testing.T) {
logger, _ := testlog.NewNullLogger()
testDir := t.TempDir()
symlinkDir := filepath.Join(testDir, "/lib/symlink")
require.NoError(t, os.MkdirAll(symlinkDir, 0755))
versionLib := filepath.Join(symlinkDir, "libcuda.so.1.2.3")
soLink := filepath.Join(symlinkDir, "libcuda.so")
sonameLink := filepath.Join(symlinkDir, "libcuda.so.1")
_, err := os.Create(versionLib)
require.NoError(t, err)
require.NoError(t, os.Symlink(versionLib, sonameLink))
require.NoError(t, os.Symlink(sonameLink, soLink))
lut := newLdcacheLocator(
WithLogger(logger),
WithRoot(testDir),
)
testCases := []struct {
description string
libname string
ldcacheMap map[string]string
expected []string
expectedError error
}{
{
description: "lib only resolves in LDCache",
libname: "libcuda.so",
ldcacheMap: map[string]string{
"libcuda.so": "/lib/from/ldcache/libcuda.so.4.5.6",
},
expected: []string{"/lib/from/ldcache/libcuda.so.4.5.6"},
},
{
description: "lib only not in LDCache returns error",
libname: "libnotcuda.so",
expectedError: ErrNotFound,
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
// We override the LDCache with a mock implementation
l := lut.(*ldcacheLocator)
l.cache = &ldcache.LDCacheMock{
LookupFunc: func(strings ...string) ([]string, []string) {
var result []string
for _, s := range strings {
if v, ok := tc.ldcacheMap[s]; ok {
result = append(result, v)
}
}
return nil, result
},
}
candidates, err := lut.Locate(tc.libname)
require.ErrorIs(t, err, tc.expectedError)
var cleanedCandidates []string
for _, c := range candidates {
// On MacOS /var and /tmp symlink to /private/var and /private/tmp which is included in the resolved path.
cleanedCandidates = append(cleanedCandidates, strings.TrimPrefix(c, "/private"))
}
require.EqualValues(t, tc.expected, cleanedCandidates)
})
}
}
func TestLibraryLocator(t *testing.T) {
logger, _ := testlog.NewNullLogger()

View File

@@ -1,5 +1,5 @@
/**
# Copyright (c) NVIDIA CORPORATION. All rights reserved.
# Copyright 2024 NVIDIA CORPORATION
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,24 +14,23 @@
# limitations under the License.
**/
package ldcache
package lookup
import "github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
import "fmt"
type empty struct {
logger logger.Interface
path string
// A null locator always returns an empty response.
type null struct {
}
var _ LDCache = (*empty)(nil)
// List always returns nil for an empty ldcache
func (e *empty) List() ([]string, []string) {
// Locate always returns empty for a null locator.
func (l *null) Locate(string) ([]string, error) {
return nil, nil
}
// Lookup logs a debug message and returns nil for an empty ldcache
func (e *empty) Lookup(prefixes ...string) ([]string, []string) {
e.logger.Debugf("Calling Lookup(%v) on empty ldcache: %v", prefixes, e.path)
return nil, nil
// A notFound locator always returns an ErrNotFound error.
type notFound struct {
}
func (l *notFound) Locate(s string) ([]string, error) {
return nil, fmt.Errorf("%s: %w", s, ErrNotFound)
}

View File

@@ -0,0 +1,81 @@
/**
# Copyright 2023 NVIDIA CORPORATION
#
# 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 root
import (
"path/filepath"
"testing"
testlog "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/require"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
"github.com/NVIDIA/nvidia-container-toolkit/internal/test"
)
func TestDriverLibrariesLocate(t *testing.T) {
logger, _ := testlog.NewNullLogger()
moduleRoot, err := test.GetModuleRoot()
require.NoError(t, err)
testCases := []struct {
rootFs string
inputs []string
expected string
expectedError error
}{
{
rootFs: "rootfs-empty",
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
expectedError: lookup.ErrNotFound,
},
{
rootFs: "rootfs-no-cache-lib64",
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
expected: "/usr/lib64/libcuda.so.999.88.77",
},
{
rootFs: "rootfs-1",
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
expected: "/lib/x86_64-linux-gnu/libcuda.so.999.88.77",
},
{
rootFs: "rootfs-2",
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
expected: "/var/lib/nvidia/lib64/libcuda.so.999.88.77",
},
}
for _, tc := range testCases {
for _, input := range tc.inputs {
t.Run(tc.rootFs+input, func(t *testing.T) {
rootfs := filepath.Join(moduleRoot, "testdata", "lookup", tc.rootFs)
driver := New(
WithLogger(logger),
WithDriverRoot(rootfs),
)
candidates, err := driver.Libraries().Locate(input)
require.ErrorIs(t, err, tc.expectedError)
if tc.expectedError == nil {
require.Equal(t, []string{filepath.Join(rootfs, tc.expected)}, candidates)
}
})
}
}
}

View File

@@ -62,6 +62,7 @@ func (p symlinkChain) Locate(pattern string) ([]string, error) {
return candidates, nil
}
var filenames []string
found := make(map[string]bool)
for len(candidates) > 0 {
candidate := candidates[0]
@@ -70,6 +71,7 @@ func (p symlinkChain) Locate(pattern string) ([]string, error) {
continue
}
found[candidate] = true
filenames = append(filenames, candidate)
target, err := symlinks.Resolve(candidate)
if err != nil {
@@ -88,11 +90,6 @@ func (p symlinkChain) Locate(pattern string) ([]string, error) {
candidates = append(candidates, target)
}
}
var filenames []string
for f := range found {
filenames = append(filenames, f)
}
return filenames, nil
}

View File

@@ -128,8 +128,7 @@ func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
// CommandLineSource returns the CLI-based containerd config loader
func CommandLineSource(hostRoot string) toml.Loader {
commandLine := chrootIfRequired(hostRoot, "containerd", "config", "dump")
return toml.FromCommandLine(commandLine[0], commandLine[1:]...)
return toml.FromCommandLine(chrootIfRequired(hostRoot, "containerd", "config", "dump")...)
}
func chrootIfRequired(hostRoot string, commandLine ...string) []string {

View File

@@ -155,8 +155,10 @@ func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
// CommandLineSource returns the CLI-based crio config loader
func CommandLineSource(hostRoot string) toml.Loader {
commandLine := chrootIfRequired(hostRoot, "crio", "status", "config")
return toml.FromCommandLine(commandLine[0], commandLine[1:]...)
return toml.LoadFirst(
toml.FromCommandLine(chrootIfRequired(hostRoot, "crio", "status", "config")...),
toml.FromCommandLine(chrootIfRequired(hostRoot, "crio-status", "config")...),
)
}
func chrootIfRequired(hostRoot string, commandLine ...string) []string {

41
pkg/config/toml/list.go Normal file
View File

@@ -0,0 +1,41 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 toml
import "errors"
type firstOf []Loader
func LoadFirst(loaders ...Loader) Loader {
return firstOf(loaders)
}
func (loaders firstOf) Load() (*Tree, error) {
var errs error
for _, loader := range loaders {
if loader == nil {
continue
}
tree, err := loader.Load()
if err != nil {
errs = errors.Join(errs, err)
continue
}
return tree, nil
}
return nil, errs
}

View File

@@ -36,12 +36,12 @@ func FromFile(path string) Loader {
// FromCommandLine creates a TOML source from the output of a shell command and its corresponding args.
// If the command is empty, an empty config is returned.
func FromCommandLine(cmd string, args ...string) Loader {
if len(cmd) == 0 {
func FromCommandLine(cmds ...string) Loader {
if len(cmds) == 0 {
return Empty
}
return &tomlCliSource{
command: cmd,
args: args,
command: cmds[0],
args: cmds[1:],
}
}

0
testdata/go.mod vendored Normal file
View File

2
testdata/lookup/rootfs-1/README.md vendored Normal file
View File

@@ -0,0 +1,2 @@
This rootfs represents a host with the CUDA driver libraries installed in
/lib/x86_64-linux-gnu. The included /etc/ld.so.cache was copied from such as system.

BIN
testdata/lookup/rootfs-1/etc/ld.so.cache vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
libcuda.so.999.88.77

3
testdata/lookup/rootfs-2/README.md vendored Normal file
View File

@@ -0,0 +1,3 @@
This rootfs represents a host with the CUDA driver libraries installed in
/var/lib/nvidia/lib64. The included /etc/ld.so.cache was generated in a container
simulating such as system.

BIN
testdata/lookup/rootfs-2/etc/ld.so.cache vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1 @@
libcuda.so.999.88.77

View File

@@ -0,0 +1 @@
The folders represents an empty rootfs.

View File

@@ -0,0 +1 @@
libcuda.so.999.88.77

View File

@@ -14,7 +14,7 @@
LIB_NAME := nvidia-container-toolkit
LIB_VERSION := 1.17.0
LIB_TAG := rc.1
LIB_TAG := rc.2
# The package version is the combination of the library version and tag.
# If the tag is specified the two components are joined with a tilde (~).