Read top-level config to propagate Root to experimental runtime

Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
Evan Lezar 2022-03-29 12:01:12 +02:00
parent 33d9c1dd57
commit d12dbd1bef
9 changed files with 206 additions and 84 deletions

View File

@ -20,12 +20,12 @@ func main() {
// run is an entry point that allows for idiomatic handling of errors
// when calling from the main function.
func run(argv []string) (rerr error) {
cfg, err := config.GetRuntimeConfig()
cfg, err := config.GetConfig()
if err != nil {
return fmt.Errorf("error loading config: %v", err)
}
err = logger.LogToFile(cfg.DebugFilePath)
err = logger.LogToFile(cfg.NVIDIAContainerRuntimeConfig.DebugFilePath)
if err != nil {
return fmt.Errorf("error opening debug log file: %v", err)
}

View File

@ -36,14 +36,13 @@ type experimental struct {
// NewExperimentalModifier creates a modifier that applied the experimental
// modications to an OCI spec if required by the runtime wrapper.
func NewExperimentalModifier(logger *logrus.Logger, cfg *config.RuntimeConfig) (oci.SpecModifier, error) {
func NewExperimentalModifier(logger *logrus.Logger, cfg *config.Config) (oci.SpecModifier, error) {
logger.Infof("Constructing modifier from config: %+v", cfg)
// TODO: We need to specify the root
root := ""
root := cfg.NVIDIAContainerCLIConfig.Root
var d discover.Discover
switch cfg.DiscoverMode {
switch cfg.NVIDIAContainerRuntimeConfig.DiscoverMode {
case "legacy":
legacyDiscoverer, err := discover.NewLegacyDiscoverer(logger, root)
if err != nil {
@ -51,7 +50,7 @@ func NewExperimentalModifier(logger *logrus.Logger, cfg *config.RuntimeConfig) (
}
d = legacyDiscoverer
default:
return nil, fmt.Errorf("invalid discover mode: %v", cfg.DiscoverMode)
return nil, fmt.Errorf("invalid discover mode: %v", cfg.NVIDIAContainerRuntimeConfig.DiscoverMode)
}
return newExperimentalModifierFromDiscoverer(logger, d)

View File

@ -32,25 +32,31 @@ func TestConstructor(t *testing.T) {
testCases := []struct {
description string
cfg *config.RuntimeConfig
cfg *config.Config
expectedError error
}{
{
description: "empty config raises error",
cfg: &config.RuntimeConfig{},
description: "empty config raises error",
cfg: &config.Config{
NVIDIAContainerRuntimeConfig: config.RuntimeConfig{},
},
expectedError: fmt.Errorf("invalid discover mode"),
},
{
description: "non-legacy discover mode raises error",
cfg: &config.RuntimeConfig{
DiscoverMode: "non-legacy",
cfg: &config.Config{
NVIDIAContainerRuntimeConfig: config.RuntimeConfig{
DiscoverMode: "non-legacy",
},
},
expectedError: fmt.Errorf("invalid discover mode"),
},
{
description: "legacy discover mode returns modifier",
cfg: &config.RuntimeConfig{
DiscoverMode: "legacy",
cfg: &config.Config{
NVIDIAContainerRuntimeConfig: config.RuntimeConfig{
DiscoverMode: "legacy",
},
},
},
}

View File

@ -32,7 +32,7 @@ const (
)
// newNVIDIAContainerRuntime is a factory method that constructs a runtime based on the selected configuration and specified logger
func newNVIDIAContainerRuntime(logger *logrus.Logger, cfg *config.RuntimeConfig, argv []string) (oci.Runtime, error) {
func newNVIDIAContainerRuntime(logger *logrus.Logger, cfg *config.Config, argv []string) (oci.Runtime, error) {
ociSpec, err := oci.NewSpec(logger, argv)
if err != nil {
return nil, fmt.Errorf("error constructing OCI specification: %v", err)
@ -45,7 +45,7 @@ func newNVIDIAContainerRuntime(logger *logrus.Logger, cfg *config.RuntimeConfig,
}
var specModifier oci.SpecModifier
if cfg.Experimental {
if cfg.NVIDIAContainerRuntimeConfig.Experimental {
specModifier, err = modifier.NewExperimentalModifier(logger, cfg)
if err != nil {
return nil, fmt.Errorf("failed to construct experimental modifier: %v", err)

View File

@ -29,19 +29,21 @@ func TestFactoryMethod(t *testing.T) {
testCases := []struct {
description string
config config.RuntimeConfig
cfg *config.Config
argv []string
expectedError bool
}{
{
description: "empty config no error",
config: config.RuntimeConfig{},
cfg: &config.Config{
NVIDIAContainerRuntimeConfig: config.RuntimeConfig{},
},
},
}
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
_, err := newNVIDIAContainerRuntime(logger, &tc.config, tc.argv)
_, err := newNVIDIAContainerRuntime(logger, tc.cfg, tc.argv)
if tc.expectedError {
require.Error(t, err)
} else {

48
internal/config/cli.go Normal file
View File

@ -0,0 +1,48 @@
/**
# 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 config
import (
"github.com/pelletier/go-toml"
)
// CLIConfig stores the options for the nvidia-container-cli
type CLIConfig struct {
Root string
}
// getCLIConfigFrom reads the nvidia container runtime config from the specified toml Tree.
func getCLIConfigFrom(toml *toml.Tree) *CLIConfig {
cfg := getDefaultCLIConfig()
if toml == nil {
return cfg
}
cfg.Root = toml.GetDefault("nvidia-container-cli.root", cfg.Root).(string)
return cfg
}
// getDefaultCLIConfig defines the default values for the config
func getDefaultCLIConfig() *CLIConfig {
c := CLIConfig{
Root: "",
}
return &c
}

99
internal/config/config.go Normal file
View File

@ -0,0 +1,99 @@
/**
# 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 config
import (
"fmt"
"io"
"os"
"path"
"github.com/pelletier/go-toml"
)
const (
configOverride = "XDG_CONFIG_HOME"
configFilePath = "nvidia-container-runtime/config.toml"
)
var (
configDir = "/etc/"
)
// Config represents the contents of the config.toml file for the NVIDIA Container Toolkit
// Note: This is currently duplicated by the HookConfig in cmd/nvidia-container-toolkit/hook_config.go
type Config struct {
NVIDIAContainerCLIConfig CLIConfig `toml:"nvidia-container-cli"`
NVIDIAContainerRuntimeConfig RuntimeConfig `toml:"nvidia-container-runtime"`
}
// GetConfig sets up the config struct. Values are read from a toml file
// or set via the environment.
func GetConfig() (*Config, error) {
if XDGConfigDir := os.Getenv(configOverride); len(XDGConfigDir) != 0 {
configDir = XDGConfigDir
}
configFilePath := path.Join(configDir, configFilePath)
tomlFile, err := os.Open(configFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open config file %v: %v", configFilePath, err)
}
defer tomlFile.Close()
cfg, err := loadConfigFrom(tomlFile)
if err != nil {
return nil, fmt.Errorf("failed to read config values: %v", err)
}
return cfg, nil
}
// loadRuntimeConfigFrom reads the config from the specified Reader
func loadConfigFrom(reader io.Reader) (*Config, error) {
toml, err := toml.LoadReader(reader)
if err != nil {
return nil, err
}
return getConfigFrom(toml), nil
}
// getConfigFrom reads the nvidia container runtime config from the specified toml Tree.
func getConfigFrom(toml *toml.Tree) *Config {
cfg := getDefaultConfig()
if toml == nil {
return cfg
}
cfg.NVIDIAContainerCLIConfig = *getCLIConfigFrom(toml)
cfg.NVIDIAContainerRuntimeConfig = *getRuntimeConfigFrom(toml)
return cfg
}
// getDefaultConfig defines the default values for the config
func getDefaultConfig() *Config {
c := Config{
NVIDIAContainerCLIConfig: *getDefaultCLIConfig(),
NVIDIAContainerRuntimeConfig: *getDefaultRuntimeConfig(),
}
return &c
}

View File

@ -17,23 +17,9 @@
package config
import (
"fmt"
"io"
"os"
"path"
"github.com/pelletier/go-toml"
)
const (
configOverride = "XDG_CONFIG_HOME"
configFilePath = "nvidia-container-runtime/config.toml"
)
var (
configDir = "/etc/"
)
// RuntimeConfig stores the config options for the NVIDIA Container Runtime
type RuntimeConfig struct {
DebugFilePath string
@ -41,39 +27,6 @@ type RuntimeConfig struct {
DiscoverMode string
}
// GetRuntimeConfig sets up the config struct. Values are read from a toml file
// or set via the environment.
func GetRuntimeConfig() (*RuntimeConfig, error) {
if XDGConfigDir := os.Getenv(configOverride); len(XDGConfigDir) != 0 {
configDir = XDGConfigDir
}
configFilePath := path.Join(configDir, configFilePath)
tomlFile, err := os.Open(configFilePath)
if err != nil {
return nil, fmt.Errorf("failed to open config file %v: %v", configFilePath, err)
}
defer tomlFile.Close()
cfg, err := loadRuntimeConfigFrom(tomlFile)
if err != nil {
return nil, fmt.Errorf("failed to read config values: %v", err)
}
return cfg, nil
}
// loadRuntimeConfigFrom reads the config from the specified Reader
func loadRuntimeConfigFrom(reader io.Reader) (*RuntimeConfig, error) {
toml, err := toml.LoadReader(reader)
if err != nil {
return nil, err
}
return getRuntimeConfigFrom(toml), nil
}
// getRuntimeConfigFrom reads the nvidia container runtime config from the specified toml Tree.
func getRuntimeConfigFrom(toml *toml.Tree) *RuntimeConfig {
cfg := getDefaultRuntimeConfig()

View File

@ -26,7 +26,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestGerRuntimeConfigWithCustomConfig(t *testing.T) {
func TestGetConfigWithCustomConfig(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)
@ -42,24 +42,29 @@ func TestGerRuntimeConfigWithCustomConfig(t *testing.T) {
defer func() { require.NoError(t, os.RemoveAll(testDir)) }()
cfg, err := GetRuntimeConfig()
cfg, err := GetConfig()
require.NoError(t, err)
require.Equal(t, cfg.DebugFilePath, "/nvidia-container-toolkit.log")
require.Equal(t, cfg.NVIDIAContainerRuntimeConfig.DebugFilePath, "/nvidia-container-toolkit.log")
}
func TestGerRuntimeConfig(t *testing.T) {
func TestGetConfig(t *testing.T) {
testCases := []struct {
description string
contents []string
expectedError error
expectedConfig *RuntimeConfig
expectedConfig *Config
}{
{
description: "empty config is default",
expectedConfig: &RuntimeConfig{
DebugFilePath: "/dev/null",
Experimental: false,
DiscoverMode: "legacy",
expectedConfig: &Config{
NVIDIAContainerCLIConfig: CLIConfig{
Root: "",
},
NVIDIAContainerRuntimeConfig: RuntimeConfig{
DebugFilePath: "/dev/null",
Experimental: false,
DiscoverMode: "legacy",
},
},
},
{
@ -69,10 +74,15 @@ func TestGerRuntimeConfig(t *testing.T) {
"nvidia-container-runtime.experimental = true",
"nvidia-container-runtime.discover-mode = \"not-legacy\"",
},
expectedConfig: &RuntimeConfig{
DebugFilePath: "/foo/bar",
Experimental: true,
DiscoverMode: "not-legacy",
expectedConfig: &Config{
NVIDIAContainerCLIConfig: CLIConfig{
Root: "",
},
NVIDIAContainerRuntimeConfig: RuntimeConfig{
DebugFilePath: "/foo/bar",
Experimental: true,
DiscoverMode: "not-legacy",
},
},
},
{
@ -83,10 +93,15 @@ func TestGerRuntimeConfig(t *testing.T) {
"experimental = true",
"discover-mode = \"not-legacy\"",
},
expectedConfig: &RuntimeConfig{
DebugFilePath: "/foo/bar",
Experimental: true,
DiscoverMode: "not-legacy",
expectedConfig: &Config{
NVIDIAContainerCLIConfig: CLIConfig{
Root: "",
},
NVIDIAContainerRuntimeConfig: RuntimeConfig{
DebugFilePath: "/foo/bar",
Experimental: true,
DiscoverMode: "not-legacy",
},
},
},
}
@ -95,7 +110,7 @@ func TestGerRuntimeConfig(t *testing.T) {
t.Run(tc.description, func(t *testing.T) {
reader := strings.NewReader(strings.Join(tc.contents, "\n"))
cfg, err := loadRuntimeConfigFrom(reader)
cfg, err := loadConfigFrom(reader)
if tc.expectedError != nil {
require.Error(t, err)
} else {