Add Relative function to Locator interface

This adds a Relative function to the Locator interface and uses
this to determine the host and container paths for located files
(and devices). This ensures that the root (e.g. the nvidia driver
root) is stripped from the container path.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
Evan Lezar 2022-06-29 16:50:17 +02:00
parent 25fd1aaf7e
commit fd135f1a8b
9 changed files with 138 additions and 24 deletions

View File

@ -49,7 +49,7 @@ func TestCharDevices(t *testing.T) {
required: []string{"required"}, required: []string{"required"},
}, },
), ),
expectedDevices: []Device{{Path: "located"}}, expectedDevices: []Device{{Path: "located", HostPath: "located"}},
}, },
{ {
description: "dev devices returns error for nil lookup", description: "dev devices returns error for nil lookup",

View File

@ -24,11 +24,13 @@ type Config struct {
// Device represents a discovered character device. // Device represents a discovered character device.
type Device struct { type Device struct {
HostPath string
Path string Path string
} }
// Mount represents a discovered mount. // Mount represents a discovered mount.
type Mount struct { type Mount struct {
HostPath string
Path string Path string
} }

View File

@ -51,7 +51,7 @@ func (d *mounts) Mounts() ([]Mount, error) {
d.Lock() d.Lock()
defer d.Unlock() defer d.Unlock()
paths := make(map[string]bool) uniqueMounts := make(map[string]Mount)
for _, candidate := range d.required { for _, candidate := range d.required {
d.logger.Debugf("Locating %v", candidate) d.logger.Debugf("Locating %v", candidate)
@ -66,20 +66,34 @@ func (d *mounts) Mounts() ([]Mount, error) {
} }
d.logger.Debugf("Located %v as %v", candidate, located) d.logger.Debugf("Located %v as %v", candidate, located)
for _, p := range located { for _, p := range located {
paths[p] = true if _, ok := uniqueMounts[p]; ok {
d.logger.Debugf("Skipping duplicate mount %v", p)
continue
}
r, err := d.lookup.Relative(p)
if err != nil {
d.logger.Warnf("Failed to get relative path of %v: %v", p, err)
continue
}
if r == "" {
r = p
}
d.logger.Infof("Selecting %v as %v", p, r)
uniqueMounts[p] = Mount{
HostPath: p,
Path: r,
}
} }
} }
var mounts []Mount var mounts []Mount
for path := range paths { for _, m := range uniqueMounts {
d.logger.Infof("Selecting %v", path) mounts = append(mounts, m)
mount := Mount{
Path: path,
}
mounts = append(mounts, mount)
} }
d.cache = mounts d.cache = mounts
return mounts, nil return d.cache, nil
} }

View File

@ -70,7 +70,7 @@ func TestMounts(t *testing.T) {
}, },
required: []string{"required"}, required: []string{"required"},
}, },
expectedMounts: []Mount{{Path: "located"}}, expectedMounts: []Mount{{Path: "located", HostPath: "located"}},
}, },
{ {
description: "mounts removes located duplicates", description: "mounts removes located duplicates",
@ -83,7 +83,7 @@ func TestMounts(t *testing.T) {
}, },
required: []string{"required0", "required1"}, required: []string{"required0", "required1"},
}, },
expectedMounts: []Mount{{Path: "located"}}, expectedMounts: []Mount{{Path: "located", HostPath: "located"}},
}, },
{ {
description: "mounts skips located errors", description: "mounts skips located errors",
@ -98,7 +98,7 @@ func TestMounts(t *testing.T) {
}, },
required: []string{"required0", "error", "required1"}, required: []string{"required0", "error", "required1"},
}, },
expectedMounts: []Mount{{Path: "required0"}, {Path: "required1"}}, expectedMounts: []Mount{{Path: "required0", HostPath: "required0"}, {Path: "required1", HostPath: "required1"}},
}, },
{ {
description: "mounts skips unlocated", description: "mounts skips unlocated",
@ -113,10 +113,10 @@ func TestMounts(t *testing.T) {
}, },
required: []string{"required0", "empty", "required1"}, required: []string{"required0", "empty", "required1"},
}, },
expectedMounts: []Mount{{Path: "required0"}, {Path: "required1"}}, expectedMounts: []Mount{{Path: "required0", HostPath: "required0"}, {Path: "required1", HostPath: "required1"}},
}, },
{ {
description: "mounts skips unlocated", description: "mounts adds multiple",
input: &mounts{ input: &mounts{
lookup: &lookup.LocatorMock{ lookup: &lookup.LocatorMock{
LocateFunc: func(s string) ([]string, error) { LocateFunc: func(s string) ([]string, error) {
@ -129,10 +129,48 @@ func TestMounts(t *testing.T) {
required: []string{"required0", "multiple", "required1"}, required: []string{"required0", "multiple", "required1"},
}, },
expectedMounts: []Mount{ expectedMounts: []Mount{
{Path: "required0"}, {Path: "required0", HostPath: "required0"},
{Path: "multiple0"}, {Path: "multiple0", HostPath: "multiple0"},
{Path: "multiple1"}, {Path: "multiple1", HostPath: "multiple1"},
{Path: "required1"}, {Path: "required1", HostPath: "required1"},
},
},
{
description: "mounts uses relative path",
input: &mounts{
lookup: &lookup.LocatorMock{
LocateFunc: func(s string) ([]string, error) {
return []string{"located"}, nil
},
RelativeFunc: func(s string) (string, error) {
return "relative", nil
},
},
required: []string{"required0", "multiple", "required1"},
},
expectedMounts: []Mount{
{Path: "relative", HostPath: "located"},
},
},
{
description: "mounts skips relative error",
input: &mounts{
lookup: &lookup.LocatorMock{
LocateFunc: func(s string) ([]string, error) {
return []string{s}, nil
},
RelativeFunc: func(s string) (string, error) {
if s == "error" {
return "", fmt.Errorf("no relative path")
}
return "relative" + s, nil
},
},
required: []string{"required0", "error", "required1"},
},
expectedMounts: []Mount{
{Path: "relativerequired0", HostPath: "required0"},
{Path: "relativerequired1", HostPath: "required1"},
}, },
}, },
} }

View File

@ -37,6 +37,9 @@ func (d device) toEdits() *cdi.ContainerEdits {
// toSpec converts a discovered Device to a CDI Spec Device. Note // toSpec converts a discovered Device to a CDI Spec Device. Note
// that missing info is filled in when edits are applied by querying the Device node. // that missing info is filled in when edits are applied by querying the Device node.
func (d device) toSpec() *specs.DeviceNode { func (d device) toSpec() *specs.DeviceNode {
// NOTE: We may want to mirror what is done in cri-o w.r.t src (Host) and dst (Container) paths
// to ensure that the right permissions are included.
// https://github.com/cri-o/cri-o/blob/ca3bb80a3dda0440659fcf8da8ed6f23211de94e/internal/config/device/device.go#L93
s := specs.DeviceNode{ s := specs.DeviceNode{
Path: d.Path, Path: d.Path,
} }

View File

@ -38,8 +38,7 @@ func (d mount) toEdits() *cdi.ContainerEdits {
// that missing info is filled in when edits are applied by querying the Mount node. // that missing info is filled in when edits are applied by querying the Mount node.
func (d mount) toSpec() *specs.Mount { func (d mount) toSpec() *specs.Mount {
s := specs.Mount{ s := specs.Mount{
HostPath: d.Path, HostPath: d.HostPath,
// TODO: We need to allow the container path to be customised
ContainerPath: d.Path, ContainerPath: d.Path,
Options: []string{ Options: []string{
"ro", "ro",

View File

@ -28,6 +28,7 @@ import (
// prefixes. The validity of a file is determined by a filter function. // prefixes. The validity of a file is determined by a filter function.
type file struct { type file struct {
logger *log.Logger logger *log.Logger
root string
prefixes []string prefixes []string
filter func(string) error filter func(string) error
} }
@ -77,6 +78,15 @@ func (p file) Locate(pattern string) ([]string, error) {
return filenames, nil return filenames, nil
} }
// Relative returns the path relative to the root for the file locator
func (p file) Relative(path string) (string, error) {
if p.root == "" || p.root == "/" {
return path, nil
}
return filepath.Rel(p.root, path)
}
// assertFile checks whether the specified path is a regular file // assertFile checks whether the specified path is a regular file
func assertFile(filename string) error { func assertFile(filename string) error {
info, err := os.Stat(filename) info, err := os.Stat(filename)

View File

@ -21,4 +21,5 @@ package lookup
// Locator defines the interface for locating files on a system. // Locator defines the interface for locating files on a system.
type Locator interface { type Locator interface {
Locate(string) ([]string, error) Locate(string) ([]string, error)
Relative(string) (string, error)
} }

View File

@ -20,6 +20,9 @@ var _ Locator = &LocatorMock{}
// LocateFunc: func(s string) ([]string, error) { // LocateFunc: func(s string) ([]string, error) {
// panic("mock out the Locate method") // panic("mock out the Locate method")
// }, // },
// RelativeFunc: func(s string) (string, error) {
// panic("mock out the Relative method")
// },
// } // }
// //
// // use mockedLocator in code that requires Locator // // use mockedLocator in code that requires Locator
@ -30,6 +33,9 @@ type LocatorMock struct {
// LocateFunc mocks the Locate method. // LocateFunc mocks the Locate method.
LocateFunc func(s string) ([]string, error) LocateFunc func(s string) ([]string, error)
// RelativeFunc mocks the Relative method.
RelativeFunc func(s string) (string, error)
// calls tracks calls to the methods. // calls tracks calls to the methods.
calls struct { calls struct {
// Locate holds details about calls to the Locate method. // Locate holds details about calls to the Locate method.
@ -37,8 +43,14 @@ type LocatorMock struct {
// S is the s argument value. // S is the s argument value.
S string S string
} }
// Relative holds details about calls to the Relative method.
Relative []struct {
// S is the s argument value.
S string
}
} }
lockLocate sync.RWMutex lockLocate sync.RWMutex
lockRelative sync.RWMutex
} }
// Locate calls LocateFunc. // Locate calls LocateFunc.
@ -75,3 +87,38 @@ func (mock *LocatorMock) LocateCalls() []struct {
mock.lockLocate.RUnlock() mock.lockLocate.RUnlock()
return calls return calls
} }
// Relative calls RelativeFunc.
func (mock *LocatorMock) Relative(s string) (string, error) {
callInfo := struct {
S string
}{
S: s,
}
mock.lockRelative.Lock()
mock.calls.Relative = append(mock.calls.Relative, callInfo)
mock.lockRelative.Unlock()
if mock.RelativeFunc == nil {
var (
sOut string
errOut error
)
return sOut, errOut
}
return mock.RelativeFunc(s)
}
// RelativeCalls gets all the calls that were made to Relative.
// Check the length with:
// len(mockedLocator.RelativeCalls())
func (mock *LocatorMock) RelativeCalls() []struct {
S string
} {
var calls []struct {
S string
}
mock.lockRelative.RLock()
calls = mock.calls.Relative
mock.lockRelative.RUnlock()
return calls
}