Merge branch 'refactor-csv-discovery' into 'main'

Refactor device discovery

See merge request nvidia/container-toolkit/container-toolkit!185
This commit is contained in:
Evan Lezar 2022-07-07 08:07:43 +00:00
commit eef016c27d
6 changed files with 174 additions and 140 deletions

View File

@ -0,0 +1,64 @@
/**
# Copyright (c) 2022, 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 discover
import (
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
"github.com/sirupsen/logrus"
)
// charDevices is a discover for a list of character devices
type charDevices mounts
var _ Discover = (*charDevices)(nil)
// NewCharDeviceDiscoverer creates a discoverer which locates the specified set of device nodes.
func NewCharDeviceDiscoverer(logger *logrus.Logger, devices []string, root string) Discover {
locator := lookup.NewCharDeviceLocator(logger, root)
return NewDeviceDiscoverer(logger, locator, devices)
}
// NewDeviceDiscoverer creates a discoverer which locates the specified set of device nodes using the specified locator.
func NewDeviceDiscoverer(logger *logrus.Logger, locator lookup.Locator, devices []string) Discover {
return &charDevices{
logger: logger,
lookup: locator,
required: devices,
}
}
// Mounts returns the discovered mounts for the charDevices.
// Since this explicitly specifies a device list, the mounts are nil.
func (d *charDevices) Mounts() ([]Mount, error) {
return nil, nil
}
// Devices returns the discovered devices for the charDevices.
// Here the device nodes are first discovered as mounts and these are converted to devices.
func (d *charDevices) Devices() ([]Device, error) {
devicesAsMounts, err := (*mounts)(d).Mounts()
if err != nil {
return nil, err
}
var devices []Device
for _, mount := range devicesAsMounts {
devices = append(devices, Device(mount))
}
return devices, nil
}

View File

@ -0,0 +1,83 @@
/**
# Copyright (c) 2021, 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 discover
import (
"fmt"
"testing"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
testlog "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/require"
)
func TestCharDevices(t *testing.T) {
logger, logHook := testlog.NewNullLogger()
testCases := []struct {
description string
input *charDevices
expectedMounts []Mount
expectedMountsError error
expectedDevicesError error
expectedDevices []Device
}{
{
description: "dev mounts are empty",
input: (*charDevices)(
&mounts{
lookup: &lookup.LocatorMock{
LocateFunc: func(string) ([]string, error) {
return []string{"located"}, nil
},
},
required: []string{"required"},
},
),
expectedDevices: []Device{{Path: "located", HostPath: "located"}},
},
{
description: "dev devices returns error for nil lookup",
input: &charDevices{},
expectedDevicesError: fmt.Errorf("no lookup defined"),
},
}
for _, tc := range testCases {
logHook.Reset()
t.Run(tc.description, func(t *testing.T) {
tc.input.logger = logger
mounts, err := tc.input.Mounts()
if tc.expectedMountsError != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.ElementsMatch(t, tc.expectedMounts, mounts)
devices, err := tc.input.Devices()
if tc.expectedDevicesError != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.ElementsMatch(t, tc.expectedDevices, devices)
})
}
}

View File

@ -24,11 +24,6 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// charDevices is a discover for a list of character devices
type charDevices mounts
var _ Discover = (*charDevices)(nil)
// NewFromCSVFiles creates a discoverer for the specified CSV files. A logger is also supplied. // NewFromCSVFiles creates a discoverer for the specified CSV files. A logger is also supplied.
// The constructed discoverer is comprised of a list, with each element in the list being associated with a // The constructed discoverer is comprised of a list, with each element in the list being associated with a
// single CSV files. // single CSV files.
@ -47,23 +42,21 @@ func NewFromCSVFiles(logger *logrus.Logger, files []string, root string) (Discov
csv.MountSpecSym: symlinkLocator, csv.MountSpecSym: symlinkLocator,
} }
var discoverers []Discover var mountSpecs []*csv.MountSpec
for _, filename := range files { for _, filename := range files {
d, err := NewFromCSVFile(logger, locators, filename) targets, err := loadCSVFile(logger, filename)
if err != nil { if err != nil {
logger.Warnf("Skipping CSV file %v: %v", filename, err) logger.Warnf("Skipping CSV file %v: %v", filename, err)
continue continue
} }
discoverers = append(discoverers, d) mountSpecs = append(mountSpecs, targets...)
} }
return &list{discoverers: discoverers}, nil return newFromMountSpecs(logger, locators, mountSpecs)
} }
// NewFromCSVFile creates a discoverer for the specified CSV file. A logger is also supplied. // loadCSVFile loads the specified CSV file and returns the list of mount specs
// The constructed discoverer is comprised of a list, with each element in the list being associated with a particular func loadCSVFile(logger *logrus.Logger, filename string) ([]*csv.MountSpec, error) {
// MountSpecType.
func NewFromCSVFile(logger *logrus.Logger, locators map[csv.MountSpecType]lookup.Locator, filename string) (Discover, error) {
// Create a discoverer for each file-kind combination // Create a discoverer for each file-kind combination
targets, err := csv.NewCSVFileParser(logger, filename).Parse() targets, err := csv.NewCSVFileParser(logger, filename).Parse()
if err != nil { if err != nil {
@ -73,7 +66,7 @@ func NewFromCSVFile(logger *logrus.Logger, locators map[csv.MountSpecType]lookup
return nil, fmt.Errorf("CSV file is empty") return nil, fmt.Errorf("CSV file is empty")
} }
return newFromMountSpecs(logger, locators, targets) return targets, nil
} }
// newFromMountSpecs creates a discoverer for the CSV file. A logger is also supplied. // newFromMountSpecs creates a discoverer for the CSV file. A logger is also supplied.
@ -99,41 +92,20 @@ func newFromMountSpecs(logger *logrus.Logger, locators map[csv.MountSpecType]loo
return nil, fmt.Errorf("no locator defined for '%v'", t) return nil, fmt.Errorf("no locator defined for '%v'", t)
} }
m := &mounts{ var m Discover
logger: logger,
lookup: locator,
required: candidatesByType[t],
}
switch t { switch t {
case csv.MountSpecDev: case csv.MountSpecDev:
// For device mount specs, we insert a charDevices into the list of discoverers. m = NewDeviceDiscoverer(logger, locator, candidatesByType[t])
discoverers = append(discoverers, (*charDevices)(m))
default: default:
discoverers = append(discoverers, m) m = &mounts{
logger: logger,
lookup: locator,
required: candidatesByType[t],
}
} }
discoverers = append(discoverers, m)
} }
return &list{discoverers: discoverers}, nil return &list{discoverers: discoverers}, nil
} }
// Mounts returns the discovered mounts for the charDevices. Since this explicitly specifies a
// device list, the mounts are nil.
func (d *charDevices) Mounts() ([]Mount, error) {
return nil, nil
}
// Devices returns the discovered devices for the charDevices. Here the device nodes are first
// discovered as mounts and these are converted to devices.
func (d *charDevices) Devices() ([]Device, error) {
devicesAsMounts, err := (*mounts)(d).Mounts()
if err != nil {
return nil, err
}
var devices []Device
for _, mount := range devicesAsMounts {
devices = append(devices, Device(mount))
}
return devices, nil
}

View File

@ -26,63 +26,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestCharDevices(t *testing.T) {
logger, logHook := testlog.NewNullLogger()
testCases := []struct {
description string
input *charDevices
expectedMounts []Mount
expectedMountsError error
expectedDevicesError error
expectedDevices []Device
}{
{
description: "dev mounts are empty",
input: (*charDevices)(
&mounts{
lookup: &lookup.LocatorMock{
LocateFunc: func(string) ([]string, error) {
return []string{"located"}, nil
},
},
required: []string{"required"},
},
),
expectedDevices: []Device{{Path: "located", HostPath: "located"}},
},
{
description: "dev devices returns error for nil lookup",
input: &charDevices{},
expectedDevicesError: fmt.Errorf("no lookup defined"),
},
}
for _, tc := range testCases {
logHook.Reset()
t.Run(tc.description, func(t *testing.T) {
tc.input.logger = logger
mounts, err := tc.input.Mounts()
if tc.expectedMountsError != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.ElementsMatch(t, tc.expectedMounts, mounts)
devices, err := tc.input.Devices()
if tc.expectedDevicesError != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.ElementsMatch(t, tc.expectedDevices, devices)
})
}
}
func TestNewFromMountSpec(t *testing.T) { func TestNewFromMountSpec(t *testing.T) {
logger, _ := testlog.NewNullLogger() logger, _ := testlog.NewNullLogger()

View File

@ -30,11 +30,11 @@ type gdsDeviceDiscoverer struct {
// NewGDSDiscoverer creates a discoverer for GPUDirect Storage devices and mounts. // NewGDSDiscoverer creates a discoverer for GPUDirect Storage devices and mounts.
func NewGDSDiscoverer(logger *logrus.Logger, root string) (Discover, error) { func NewGDSDiscoverer(logger *logrus.Logger, root string) (Discover, error) {
devices := &mounts{ devices := NewCharDeviceDiscoverer(
logger: logger, logger,
lookup: lookup.NewCharDeviceLocator(logger, root), []string{"/dev/nvidia-fs*"},
required: []string{"/dev/nvidia-fs*"}, root,
} )
udev := &mounts{ udev := &mounts{
logger: logger, logger: logger,
@ -59,17 +59,7 @@ func NewGDSDiscoverer(logger *logrus.Logger, root string) (Discover, error) {
// Devices discovers the nvidia-fs device nodes for use with GPUDirect Storage // Devices discovers the nvidia-fs device nodes for use with GPUDirect Storage
func (d *gdsDeviceDiscoverer) Devices() ([]Device, error) { func (d *gdsDeviceDiscoverer) Devices() ([]Device, error) {
devicesAsMounts, err := d.devices.Mounts() return d.devices.Devices()
if err != nil {
d.logger.Debugf("Could not locate GDS devices: %v", err)
return nil, nil
}
var devices []Device
for _, mount := range devicesAsMounts {
devices = append(devices, Device(mount))
}
return devices, nil
} }
// Mounts discovers the required mounts for GPUDirect Storage. // Mounts discovers the required mounts for GPUDirect Storage.

View File

@ -17,37 +17,19 @@
package discover package discover
import ( import (
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
type mofedDeviceDiscoverer mounts
// NewMOFEDDiscoverer creates a discoverer for MOFED devices. // NewMOFEDDiscoverer creates a discoverer for MOFED devices.
func NewMOFEDDiscoverer(logger *logrus.Logger, root string) (Discover, error) { func NewMOFEDDiscoverer(logger *logrus.Logger, root string) (Discover, error) {
devices := &mofedDeviceDiscoverer{ devices := NewCharDeviceDiscoverer(
logger: logger, logger,
lookup: lookup.NewCharDeviceLocator(logger, root), []string{
required: []string{
"/dev/infiniband/uverbs*", "/dev/infiniband/uverbs*",
"/dev/infiniband/rdma_cm", "/dev/infiniband/rdma_cm",
}, },
} root,
)
return devices, nil
}
// Devices discovers the uverbs* and rdma_cm device nodes for use with GPUDirect Storage and the MOFED stack.
func (d *mofedDeviceDiscoverer) Devices() ([]Device, error) {
devicesAsMounts, err := (*mounts)(d).Mounts()
if err != nil {
d.logger.Debugf("Could not locate MOFED devices: %v", err)
return nil, nil
}
var devices []Device
for _, mount := range devicesAsMounts {
devices = append(devices, Device(mount))
}
return devices, nil return devices, nil
} }