diff --git a/Makefile b/Makefile index 18b05684..bc1a4e5f 100644 --- a/Makefile +++ b/Makefile @@ -90,11 +90,7 @@ ineffassign: lint: # We use `go list -f '{{.Dir}}' $(MODULE)/...` to skip the `vendor` folder. - go list -f '{{.Dir}}' $(MODULE)/... | grep -v /internal/ | xargs golint -set_exit_status - -lint-internal: -# We use `go list -f '{{.Dir}}' $(MODULE)/...` to skip the `vendor` folder. - go list -f '{{.Dir}}' $(MODULE)/internal/... | xargs golint -set_exit_status + go list -f '{{.Dir}}' $(MODULE)/... | xargs golint -set_exit_status misspell: misspell $(MODULE)/... diff --git a/cmd/nvidia-container-runtime/README.md b/cmd/nvidia-container-runtime/README.md new file mode 100644 index 00000000..af4e1040 --- /dev/null +++ b/cmd/nvidia-container-runtime/README.md @@ -0,0 +1,24 @@ +# The NVIDIA Container Runtime + +The NVIDIA Container Runtime is a shim for OCI-compliant low-level runtimes such as [runc](https://github.com/opencontainers/runc). When a `create` command is detected, the incoming [OCI runtime specification](https://github.com/opencontainers/runtime-spec) is modified in place and the command is forwarded to the low-level runtime. + +## Standard Mode + +In the standard mode configuration, the NVIDIA Container Runtime adds a [`prestart` hook](https://github.com/opencontainers/runtime-spec/blob/master/config.md#prestart) to the incomming OCI specification that invokes the NVIDIA Container Runtime Hook for all containers created. This hook checks whether NVIDIA devices are requested and ensures GPU access is configured using the `nvidia-container-cli` from project [libnvidia-container](https://github.com/NVIDIA/libnvidia-container). + +## Experimental Mode + +The NVIDIA Container Runtime can be configured in an experimental mode by setting the following options in the runtime's `config.toml` file: + +```toml +[nvidia-container-runtime] +experimental = true +``` + +When this setting is enabled, the modifications made to the OCI specification are controlled by the `nvidia-container-runtime.discover-mode` option, with the following mode supported: + +* `"legacy"`: This mode mirrors the behaviour of the standard mode, inserting the NVIDIA Container Runtime Hook as a `prestart` hook into the container's OCI specification. + +### Notes on using the docker CLI + +The `docker` CLI supports the `--gpus` flag to select GPUs for inclusion in a container. Since specifying this flag inserts the same NVIDIA Container Runtime Hook into the OCI runtime specification. When experimental mode is activated, the NVIDIA Container Runtime detects the presence of the hook and raises an error. This requirement will be relaxed in the near future. diff --git a/cmd/nvidia-container-runtime/main.go b/cmd/nvidia-container-runtime/main.go index 69f822bf..b0df4f87 100644 --- a/cmd/nvidia-container-runtime/main.go +++ b/cmd/nvidia-container-runtime/main.go @@ -3,18 +3,8 @@ package main import ( "fmt" "os" - "path" - "github.com/pelletier/go-toml" -) - -const ( - configOverride = "XDG_CONFIG_HOME" - configFilePath = "nvidia-container-runtime/config.toml" -) - -var ( - configDir = "/etc/" + "github.com/NVIDIA/nvidia-container-toolkit/internal/config" ) var logger = NewLogger() @@ -22,7 +12,7 @@ var logger = NewLogger() func main() { err := run(os.Args) if err != nil { - logger.Errorf("Error running %v: %v", os.Args, err) + logger.Errorf("%v", err) os.Exit(1) } } @@ -30,57 +20,28 @@ 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 := getConfig() + logger.Debugf("Running %v", argv) + 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) } defer func() { // We capture and log a returning error before closing the log file. if rerr != nil { - logger.Errorf("Error running %v: %v", argv, rerr) + logger.Errorf("%v", rerr) } logger.CloseFile() }() - r, err := newNVIDIAContainerRuntime(logger.Logger, cfg, argv) + runtime, err := newNVIDIAContainerRuntime(logger.Logger, cfg, argv) if err != nil { - return fmt.Errorf("error creating runtime: %v", err) + return fmt.Errorf("failed to create NVIDIA Container Runtime: %v", err) } - return r.Exec(argv) -} - -type config struct { - debugFilePath string -} - -// getConfig sets up the config struct. Values are read from a toml file -// or set via the environment. -func getConfig() (*config, error) { - cfg := &config{} - - if XDGConfigDir := os.Getenv(configOverride); len(XDGConfigDir) != 0 { - configDir = XDGConfigDir - } - - configFilePath := path.Join(configDir, configFilePath) - - tomlContent, err := os.ReadFile(configFilePath) - if err != nil { - return nil, err - } - - toml, err := toml.Load(string(tomlContent)) - if err != nil { - return nil, err - } - - cfg.debugFilePath = toml.GetDefault("nvidia-container-runtime.debug", "/dev/null").(string) - - return cfg, nil + return runtime.Exec(argv) } diff --git a/cmd/nvidia-container-runtime/main_test.go b/cmd/nvidia-container-runtime/main_test.go index 0b576661..d6ba14ae 100644 --- a/cmd/nvidia-container-runtime/main_test.go +++ b/cmd/nvidia-container-runtime/main_test.go @@ -245,24 +245,3 @@ func nvidiaHookCount(hooks *specs.Hooks) int { } return count } - -func TestGetConfigWithCustomConfig(t *testing.T) { - wd, err := os.Getwd() - require.NoError(t, err) - - // By default debug is disabled - contents := []byte("[nvidia-container-runtime]\ndebug = \"/nvidia-container-toolkit.log\"") - testDir := filepath.Join(wd, "test") - filename := filepath.Join(testDir, configFilePath) - - os.Setenv(configOverride, testDir) - - require.NoError(t, os.MkdirAll(filepath.Dir(filename), 0766)) - require.NoError(t, ioutil.WriteFile(filename, contents, 0766)) - - defer func() { require.NoError(t, os.RemoveAll(testDir)) }() - - cfg, err := getConfig() - require.NoError(t, err) - require.Equal(t, cfg.debugFilePath, "/nvidia-container-toolkit.log") -} diff --git a/cmd/nvidia-container-runtime/modifier/experimental.go b/cmd/nvidia-container-runtime/modifier/experimental.go new file mode 100644 index 00000000..d5a42c05 --- /dev/null +++ b/cmd/nvidia-container-runtime/modifier/experimental.go @@ -0,0 +1,82 @@ +/** +# 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 modifier + +import ( + "fmt" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/config" + "github.com/NVIDIA/nvidia-container-toolkit/internal/discover" + "github.com/NVIDIA/nvidia-container-toolkit/internal/edits" + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +// experiemental represents the modifications required by the experimental runtime +type experimental struct { + logger *logrus.Logger + discoverer discover.Discover +} + +// NewExperimentalModifier creates a modifier that applies the experimental +// modications to an OCI spec if required by the runtime wrapper. +func NewExperimentalModifier(logger *logrus.Logger, cfg *config.Config) (oci.SpecModifier, error) { + logger.Infof("Constructing modifier from config: %+v", cfg) + + root := cfg.NVIDIAContainerCLIConfig.Root + + var d discover.Discover + switch cfg.NVIDIAContainerRuntimeConfig.DiscoverMode { + case "legacy": + legacyDiscoverer, err := discover.NewLegacyDiscoverer(logger, root) + if err != nil { + return nil, fmt.Errorf("failed to create legacy discoverer: %v", err) + } + d = legacyDiscoverer + default: + return nil, fmt.Errorf("invalid discover mode: %v", cfg.NVIDIAContainerRuntimeConfig.DiscoverMode) + } + + return newExperimentalModifierFromDiscoverer(logger, d) +} + +// newExperimentalModifierFromDiscoverer created a modifier that aplies the discovered +// modifications to an OCI spec if require by the runtime wrapper. +func newExperimentalModifierFromDiscoverer(logger *logrus.Logger, d discover.Discover) (oci.SpecModifier, error) { + m := experimental{ + logger: logger, + discoverer: d, + } + return &m, nil +} + +// Modify applies the required modifications to the incomming OCI spec. These modifications +// are applied in-place. +func (m experimental) Modify(spec *specs.Spec) error { + err := nvidiaContainerRuntimeHookRemover{m.logger}.Modify(spec) + if err != nil { + return fmt.Errorf("failed to remove existing hooks: %v", err) + } + + specEdits, err := edits.NewSpecEdits(m.logger, m.discoverer) + if err != nil { + return fmt.Errorf("failed to get required container edits: %v", err) + } + + return specEdits.Modify(spec) +} diff --git a/cmd/nvidia-container-runtime/modifier/experimental_test.go b/cmd/nvidia-container-runtime/modifier/experimental_test.go new file mode 100644 index 00000000..aefcff17 --- /dev/null +++ b/cmd/nvidia-container-runtime/modifier/experimental_test.go @@ -0,0 +1,264 @@ +/** +# 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 modifier + +import ( + "fmt" + "testing" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/config" + "github.com/NVIDIA/nvidia-container-toolkit/internal/discover" + "github.com/opencontainers/runtime-spec/specs-go" + testlog "github.com/sirupsen/logrus/hooks/test" + "github.com/stretchr/testify/require" +) + +func TestConstructor(t *testing.T) { + logger, _ := testlog.NewNullLogger() + + testCases := []struct { + description string + cfg *config.Config + expectedError error + }{ + { + 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.Config{ + NVIDIAContainerRuntimeConfig: config.RuntimeConfig{ + DiscoverMode: "non-legacy", + }, + }, + expectedError: fmt.Errorf("invalid discover mode"), + }, + { + description: "legacy discover mode returns modifier", + cfg: &config.Config{ + NVIDIAContainerRuntimeConfig: config.RuntimeConfig{ + DiscoverMode: "legacy", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + _, err := NewExperimentalModifier(logger, tc.cfg) + if tc.expectedError != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestExperimentalModifier(t *testing.T) { + logger, _ := testlog.NewNullLogger() + + testCases := []struct { + description string + discover *discover.DiscoverMock + spec *specs.Spec + expectedError error + expectedSpec *specs.Spec + }{ + { + description: "empty discoverer does not modify spec", + discover: &discover.DiscoverMock{}, + }, + { + description: "failed hooks discoverer returns error", + discover: &discover.DiscoverMock{ + HooksFunc: func() ([]discover.Hook, error) { + return nil, fmt.Errorf("discover.Hooks error") + }, + }, + expectedError: fmt.Errorf("discover.Hooks error"), + }, + { + description: "discovered hooks are injected into spec", + spec: &specs.Spec{}, + discover: &discover.DiscoverMock{ + HooksFunc: func() ([]discover.Hook, error) { + hooks := []discover.Hook{ + { + Lifecycle: "prestart", + Path: "/hook/a", + Args: []string{"/hook/a", "arga"}, + }, + { + Lifecycle: "createContainer", + Path: "/hook/b", + Args: []string{"/hook/b", "argb"}, + }, + } + return hooks, nil + }, + }, + expectedSpec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/hook/a", + Args: []string{"/hook/a", "arga"}, + }, + }, + CreateContainer: []specs.Hook{ + { + Path: "/hook/b", + Args: []string{"/hook/b", "argb"}, + }, + }, + }, + }, + }, + { + description: "existing hooks are maintained", + spec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/hook/a", + Args: []string{"/hook/a", "arga"}, + }, + }, + }, + }, + discover: &discover.DiscoverMock{ + HooksFunc: func() ([]discover.Hook, error) { + hooks := []discover.Hook{ + { + Lifecycle: "prestart", + Path: "/hook/b", + Args: []string{"/hook/b", "argb"}, + }, + } + return hooks, nil + }, + }, + expectedSpec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/hook/a", + Args: []string{"/hook/a", "arga"}, + }, + { + Path: "/hook/b", + Args: []string{"/hook/b", "argb"}, + }, + }, + }, + }, + }, + { + description: "modification fails for existing nvidia-container-runtime-hook", + spec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/path/to/nvidia-container-runtime-hook", + Args: []string{"/path/to/nvidia-container-runtime-hook", "prestart"}, + }, + }, + }, + }, + discover: &discover.DiscoverMock{ + HooksFunc: func() ([]discover.Hook, error) { + hooks := []discover.Hook{ + { + Lifecycle: "prestart", + Path: "/hook/b", + Args: []string{"/hook/b", "argb"}, + }, + } + return hooks, nil + }, + }, + expectedError: fmt.Errorf("nvidia-container-runtime-hook already exists"), + expectedSpec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/path/to/nvidia-container-runtime-hook", + Args: []string{"/path/to/nvidia-container-runtime-hook", "prestart"}, + }, + }, + }, + }, + }, + { + description: "modification fails for existing nvidia-container-toolkit", + spec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/path/to/nvidia-container-toolkit", + Args: []string{"/path/to/nvidia-container-toolkit", "prestart"}, + }, + }, + }, + }, + discover: &discover.DiscoverMock{ + HooksFunc: func() ([]discover.Hook, error) { + hooks := []discover.Hook{ + { + Lifecycle: "prestart", + Path: "/hook/b", + Args: []string{"/hook/b", "argb"}, + }, + } + return hooks, nil + }, + }, + expectedError: fmt.Errorf("nvidia-container-toolkit already exists"), + expectedSpec: &specs.Spec{ + Hooks: &specs.Hooks{ + Prestart: []specs.Hook{ + { + Path: "/path/to/nvidia-container-toolkit", + Args: []string{"/path/to/nvidia-container-toolkit", "prestart"}, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + m, err := newExperimentalModifierFromDiscoverer(logger, tc.discover) + require.NoError(t, err) + + err = m.Modify(tc.spec) + if tc.expectedError != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + require.EqualValues(t, tc.expectedSpec, tc.spec) + }) + } +} diff --git a/cmd/nvidia-container-runtime/modifier/hook_remover.go b/cmd/nvidia-container-runtime/modifier/hook_remover.go new file mode 100644 index 00000000..7df4b2f1 --- /dev/null +++ b/cmd/nvidia-container-runtime/modifier/hook_remover.go @@ -0,0 +1,70 @@ +/** +# 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 modifier + +import ( + "fmt" + "path/filepath" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +// nvidiaContainerRuntimeHookRemover is a spec modifer that detects and removes inserted nvidia-container-runtime hooks +type nvidiaContainerRuntimeHookRemover struct { + logger *logrus.Logger +} + +var _ oci.SpecModifier = (*nvidiaContainerRuntimeHookRemover)(nil) + +// Modify removes any NVIDIA Container Runtime hooks from the provided spec +func (m nvidiaContainerRuntimeHookRemover) Modify(spec *specs.Spec) error { + if spec == nil { + return nil + } + + if spec.Hooks == nil { + return nil + } + + if len(spec.Hooks.Prestart) == 0 { + return nil + } + + for _, hook := range spec.Hooks.Prestart { + if isNVIDIAContainerRuntimeHook(&hook) { + return fmt.Errorf("spec already contains required 'prestart' hook") + } + } + + return nil +} + +// isNVIDIAContainerRuntimeHook checks if the provided hook is an nvidia-container-runtime-hook +// or nvidia-container-toolkit hook. These are included, for example, by the non-experimental +// nvidia-container-runtime or docker when specifying the --gpus flag. +func isNVIDIAContainerRuntimeHook(hook *specs.Hook) bool { + bins := map[string]struct{}{ + nvidiaContainerRuntimeHookExecutable: {}, + nvidiaContainerToolkitExecutable: {}, + } + + _, exists := bins[filepath.Base(hook.Path)] + + return exists +} diff --git a/cmd/nvidia-container-runtime/modifier/stable.go b/cmd/nvidia-container-runtime/modifier/stable.go index 37dfa1e7..fa0fb8f1 100644 --- a/cmd/nvidia-container-runtime/modifier/stable.go +++ b/cmd/nvidia-container-runtime/modifier/stable.go @@ -27,7 +27,10 @@ import ( ) const ( - hookDefaultFilePath = "/usr/bin/nvidia-container-runtime-hook" + nvidiaContainerRuntimeHookExecutable = "nvidia-container-runtime-hook" + nvidiaContainerRuntimeHookDefaultPath = "/usr/bin/nvidia-container-runtime-hook" + + nvidiaContainerToolkitExecutable = "nvidia-container-toolkit" ) // NewStableRuntimeModifier creates an OCI spec modifier that inserts the NVIDIA Container Runtime Hook into an OCI @@ -47,9 +50,9 @@ type stableRuntimeModifier struct { // Modify applies the required modification to the incoming OCI spec, inserting the nvidia-container-runtime-hook // as a prestart hook. func (m stableRuntimeModifier) Modify(spec *specs.Spec) error { - path, err := exec.LookPath("nvidia-container-runtime-hook") + path, err := exec.LookPath(nvidiaContainerRuntimeHookExecutable) if err != nil { - path = hookDefaultFilePath + path = nvidiaContainerRuntimeHookDefaultPath _, err = os.Stat(path) if err != nil { return err @@ -63,7 +66,7 @@ func (m stableRuntimeModifier) Modify(spec *specs.Spec) error { spec.Hooks = &specs.Hooks{} } else if len(spec.Hooks.Prestart) != 0 { for _, hook := range spec.Hooks.Prestart { - if strings.Contains(hook.Path, "nvidia-container-runtime-hook") { + if strings.Contains(hook.Path, nvidiaContainerRuntimeHookExecutable) { m.logger.Infof("existing nvidia prestart hook found in OCI spec") return nil } diff --git a/cmd/nvidia-container-runtime/runtime_factory.go b/cmd/nvidia-container-runtime/runtime_factory.go index 8b547935..8eb23302 100644 --- a/cmd/nvidia-container-runtime/runtime_factory.go +++ b/cmd/nvidia-container-runtime/runtime_factory.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-container-runtime/modifier" + "github.com/NVIDIA/nvidia-container-toolkit/internal/config" "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" "github.com/NVIDIA/nvidia-container-toolkit/internal/runtime" "github.com/sirupsen/logrus" @@ -31,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, 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) @@ -43,7 +44,10 @@ func newNVIDIAContainerRuntime(logger *logrus.Logger, cfg *config, argv []string return nil, fmt.Errorf("error constructing low-level runtime: %v", err) } - specModifier := modifier.NewStableRuntimeModifier(logger) + specModifier, err := newSpecModifier(logger, cfg) + if err != nil { + return nil, fmt.Errorf("failed to construct OCI spec modifier: %v", err) + } // Create the wrapping runtime with the specified modifier r := runtime.NewModifyingRuntimeWrapper( @@ -55,3 +59,12 @@ func newNVIDIAContainerRuntime(logger *logrus.Logger, cfg *config, argv []string return r, nil } + +// newSpecModifier is a factory method that creates constructs an OCI spec modifer based on the provided config. +func newSpecModifier(logger *logrus.Logger, cfg *config.Config) (oci.SpecModifier, error) { + if !cfg.NVIDIAContainerRuntimeConfig.Experimental { + return modifier.NewStableRuntimeModifier(logger), nil + } + + return modifier.NewExperimentalModifier(logger, cfg) +} diff --git a/cmd/nvidia-container-runtime/runtime_factory_test.go b/cmd/nvidia-container-runtime/runtime_factory_test.go index b29e07bf..145970d7 100644 --- a/cmd/nvidia-container-runtime/runtime_factory_test.go +++ b/cmd/nvidia-container-runtime/runtime_factory_test.go @@ -19,6 +19,7 @@ package main import ( "testing" + "github.com/NVIDIA/nvidia-container-toolkit/internal/config" testlog "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/require" ) @@ -28,19 +29,21 @@ func TestFactoryMethod(t *testing.T) { testCases := []struct { description string - config config + cfg *config.Config argv []string expectedError bool }{ { description: "empty config no error", - config: config{}, + 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 { diff --git a/internal/config/cli.go b/internal/config/cli.go new file mode 100644 index 00000000..5cd95751 --- /dev/null +++ b/internal/config/cli.go @@ -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" +) + +// ContainerCLIConfig stores the options for the nvidia-container-cli +type ContainerCLIConfig struct { + Root string +} + +// getContainerCLIConfigFrom reads the nvidia container runtime config from the specified toml Tree. +func getContainerCLIConfigFrom(toml *toml.Tree) *ContainerCLIConfig { + cfg := getDefaultContainerCLIConfig() + + if toml == nil { + return cfg + } + + cfg.Root = toml.GetDefault("nvidia-container-cli.root", cfg.Root).(string) + + return cfg +} + +// getDefaultContainerCLIConfig defines the default values for the config +func getDefaultContainerCLIConfig() *ContainerCLIConfig { + c := ContainerCLIConfig{ + Root: "", + } + + return &c +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..8e328487 --- /dev/null +++ b/internal/config/config.go @@ -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 ContainerCLIConfig `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 = *getContainerCLIConfigFrom(toml) + cfg.NVIDIAContainerRuntimeConfig = *getRuntimeConfigFrom(toml) + + return cfg +} + +// getDefaultConfig defines the default values for the config +func getDefaultConfig() *Config { + c := Config{ + NVIDIAContainerCLIConfig: *getDefaultContainerCLIConfig(), + NVIDIAContainerRuntimeConfig: *getDefaultRuntimeConfig(), + } + + return &c +} diff --git a/internal/config/runtime.go b/internal/config/runtime.go new file mode 100644 index 00000000..c0c43011 --- /dev/null +++ b/internal/config/runtime.go @@ -0,0 +1,54 @@ +/** +# 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" +) + +// RuntimeConfig stores the config options for the NVIDIA Container Runtime +type RuntimeConfig struct { + DebugFilePath string + Experimental bool + DiscoverMode string +} + +// getRuntimeConfigFrom reads the nvidia container runtime config from the specified toml Tree. +func getRuntimeConfigFrom(toml *toml.Tree) *RuntimeConfig { + cfg := getDefaultRuntimeConfig() + + if toml == nil { + return cfg + } + + cfg.DebugFilePath = toml.GetDefault("nvidia-container-runtime.debug", cfg.DebugFilePath).(string) + cfg.Experimental = toml.GetDefault("nvidia-container-runtime.experimental", cfg.Experimental).(bool) + cfg.DiscoverMode = toml.GetDefault("nvidia-container-runtime.discover-mode", cfg.DiscoverMode).(string) + + return cfg +} + +// getDefaultRuntimeConfig defines the default values for the config +func getDefaultRuntimeConfig() *RuntimeConfig { + c := RuntimeConfig{ + DebugFilePath: "/dev/null", + Experimental: false, + DiscoverMode: "legacy", + } + + return &c +} diff --git a/internal/config/runtime_test.go b/internal/config/runtime_test.go new file mode 100644 index 00000000..32ea7a0b --- /dev/null +++ b/internal/config/runtime_test.go @@ -0,0 +1,123 @@ +/** +# 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 ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetConfigWithCustomConfig(t *testing.T) { + wd, err := os.Getwd() + require.NoError(t, err) + + // By default debug is disabled + contents := []byte("[nvidia-container-runtime]\ndebug = \"/nvidia-container-toolkit.log\"") + testDir := filepath.Join(wd, "test") + filename := filepath.Join(testDir, configFilePath) + + os.Setenv(configOverride, testDir) + + require.NoError(t, os.MkdirAll(filepath.Dir(filename), 0766)) + require.NoError(t, ioutil.WriteFile(filename, contents, 0766)) + + defer func() { require.NoError(t, os.RemoveAll(testDir)) }() + + cfg, err := GetConfig() + require.NoError(t, err) + require.Equal(t, cfg.NVIDIAContainerRuntimeConfig.DebugFilePath, "/nvidia-container-toolkit.log") +} + +func TestGetConfig(t *testing.T) { + testCases := []struct { + description string + contents []string + expectedError error + expectedConfig *Config + }{ + { + description: "empty config is default", + expectedConfig: &Config{ + NVIDIAContainerCLIConfig: ContainerCLIConfig{ + Root: "", + }, + NVIDIAContainerRuntimeConfig: RuntimeConfig{ + DebugFilePath: "/dev/null", + Experimental: false, + DiscoverMode: "legacy", + }, + }, + }, + { + description: "config options set inline", + contents: []string{ + "nvidia-container-runtime.debug = \"/foo/bar\"", + "nvidia-container-runtime.experimental = true", + "nvidia-container-runtime.discover-mode = \"not-legacy\"", + }, + expectedConfig: &Config{ + NVIDIAContainerCLIConfig: ContainerCLIConfig{ + Root: "", + }, + NVIDIAContainerRuntimeConfig: RuntimeConfig{ + DebugFilePath: "/foo/bar", + Experimental: true, + DiscoverMode: "not-legacy", + }, + }, + }, + { + description: "config options set in section", + contents: []string{ + "[nvidia-container-runtime]", + "debug = \"/foo/bar\"", + "experimental = true", + "discover-mode = \"not-legacy\"", + }, + expectedConfig: &Config{ + NVIDIAContainerCLIConfig: ContainerCLIConfig{ + Root: "", + }, + NVIDIAContainerRuntimeConfig: RuntimeConfig{ + DebugFilePath: "/foo/bar", + Experimental: true, + DiscoverMode: "not-legacy", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + reader := strings.NewReader(strings.Join(tc.contents, "\n")) + + cfg, err := loadConfigFrom(reader) + if tc.expectedError != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + require.EqualValues(t, tc.expectedConfig, cfg) + }) + } +} diff --git a/internal/discover/discover.go b/internal/discover/discover.go new file mode 100644 index 00000000..4858b282 --- /dev/null +++ b/internal/discover/discover.go @@ -0,0 +1,30 @@ +/* +# Copyright (c) 2021-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 + +// Hook represents a discovered hook. +type Hook struct { + Lifecycle string + Path string + Args []string +} + +//go:generate moq -stub -out discover_mock.go . Discover +// Discover defines an interface for discovering the devices, mounts, and hooks available on a system +type Discover interface { + Hooks() ([]Hook, error) +} diff --git a/internal/discover/discover_mock.go b/internal/discover/discover_mock.go new file mode 100644 index 00000000..0a0f211f --- /dev/null +++ b/internal/discover/discover_mock.go @@ -0,0 +1,70 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package discover + +import ( + "sync" +) + +// Ensure, that DiscoverMock does implement Discover. +// If this is not the case, regenerate this file with moq. +var _ Discover = &DiscoverMock{} + +// DiscoverMock is a mock implementation of Discover. +// +// func TestSomethingThatUsesDiscover(t *testing.T) { +// +// // make and configure a mocked Discover +// mockedDiscover := &DiscoverMock{ +// HooksFunc: func() ([]Hook, error) { +// panic("mock out the Hooks method") +// }, +// } +// +// // use mockedDiscover in code that requires Discover +// // and then make assertions. +// +// } +type DiscoverMock struct { + // HooksFunc mocks the Hooks method. + HooksFunc func() ([]Hook, error) + + // calls tracks calls to the methods. + calls struct { + // Hooks holds details about calls to the Hooks method. + Hooks []struct { + } + } + lockHooks sync.RWMutex +} + +// Hooks calls HooksFunc. +func (mock *DiscoverMock) Hooks() ([]Hook, error) { + callInfo := struct { + }{} + mock.lockHooks.Lock() + mock.calls.Hooks = append(mock.calls.Hooks, callInfo) + mock.lockHooks.Unlock() + if mock.HooksFunc == nil { + var ( + hooksOut []Hook + errOut error + ) + return hooksOut, errOut + } + return mock.HooksFunc() +} + +// HooksCalls gets all the calls that were made to Hooks. +// Check the length with: +// len(mockedDiscover.HooksCalls()) +func (mock *DiscoverMock) HooksCalls() []struct { +} { + var calls []struct { + } + mock.lockHooks.RLock() + calls = mock.calls.Hooks + mock.lockHooks.RUnlock() + return calls +} diff --git a/internal/discover/legacy.go b/internal/discover/legacy.go new file mode 100644 index 00000000..53f6ffc1 --- /dev/null +++ b/internal/discover/legacy.go @@ -0,0 +1,73 @@ +/** +# 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/container-orchestrated-devices/container-device-interface/pkg/cdi" + "github.com/sirupsen/logrus" +) + +type legacy struct { + logger *logrus.Logger + lookup lookup.Locator +} + +const ( + nvidiaContainerRuntimeHookExecutable = "nvidia-container-runtime-hook" + hookDefaultFilePath = "/usr/bin/nvidia-container-runtime-hook" +) + +var _ Discover = (*legacy)(nil) + +// NewLegacyDiscoverer creates a discoverer for the legacy runtime +func NewLegacyDiscoverer(logger *logrus.Logger, root string) (Discover, error) { + d := legacy{ + logger: logger, + lookup: lookup.NewExecutaleLocator(logger, root), + } + + return &d, nil +} + +// Hooks returns the "legacy" NVIDIA Container Runtime hook. This hook calls out +// to the nvidia-container-cli to make modifications to the container as defined +// in libnvidia-container. +func (d legacy) Hooks() ([]Hook, error) { + var hooks []Hook + + hookPath := hookDefaultFilePath + targets, err := d.lookup.Locate(nvidiaContainerRuntimeHookExecutable) + if err != nil { + d.logger.Warnf("Failed to locate %v: %v", nvidiaContainerRuntimeHookExecutable, err) + } else if len(targets) == 0 { + d.logger.Warnf("%v not found", nvidiaContainerRuntimeHookExecutable) + } else { + d.logger.Debugf("Found %v candidates: %v", nvidiaContainerRuntimeHookExecutable, targets) + hookPath = targets[0] + } + d.logger.Debugf("Using NVIDIA Container Runtime Hook path %v", hookPath) + + args := []string{hookPath, "prestart"} + legacyHook := Hook{ + Lifecycle: cdi.PrestartHook, + Path: hookPath, + Args: args, + } + hooks = append(hooks, legacyHook) + return hooks, nil +} diff --git a/internal/edits/edits.go b/internal/edits/edits.go new file mode 100644 index 00000000..36fecd81 --- /dev/null +++ b/internal/edits/edits.go @@ -0,0 +1,66 @@ +/** +# 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 edits + +import ( + "fmt" + + "github.com/NVIDIA/nvidia-container-toolkit/internal/discover" + "github.com/NVIDIA/nvidia-container-toolkit/internal/oci" + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" + ociSpecs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" +) + +type edits struct { + cdi.ContainerEdits + logger *logrus.Logger +} + +// NewSpecEdits creates a SpecModifier that defines the required OCI spec edits (as CDI ContainerEdits) from the specified +// discoverer. +func NewSpecEdits(logger *logrus.Logger, d discover.Discover) (oci.SpecModifier, error) { + hooks, err := d.Hooks() + if err != nil { + return nil, fmt.Errorf("failed to discover hooks: %v", err) + } + + c := cdi.ContainerEdits{} + for _, h := range hooks { + c.Append(hook(h).toEdits()) + } + + e := edits{ + ContainerEdits: c, + logger: logger, + } + + return &e, nil +} + +// Modify applies the defined edits to the incoming OCI spec +func (e *edits) Modify(spec *ociSpecs.Spec) error { + if e == nil || e.ContainerEdits.ContainerEdits == nil { + return nil + } + + e.logger.Infof("Hooks:") + for _, hook := range e.Hooks { + e.logger.Infof("Injecting %v", hook.Args) + } + return e.Apply(spec) +} diff --git a/internal/edits/hook.go b/internal/edits/hook.go new file mode 100644 index 00000000..990d8565 --- /dev/null +++ b/internal/edits/hook.go @@ -0,0 +1,46 @@ +/** +# 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 edits + +import ( + "github.com/NVIDIA/nvidia-container-toolkit/internal/discover" + "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" + "github.com/container-orchestrated-devices/container-device-interface/specs-go" +) + +type hook discover.Hook + +// toEdits converts a discovered hook to CDI Container Edits. +func (d hook) toEdits() *cdi.ContainerEdits { + e := cdi.ContainerEdits{ + ContainerEdits: &specs.ContainerEdits{ + Hooks: []*specs.Hook{d.toSpec()}, + }, + } + return &e +} + +// toSpec converts a discovered Hook to a CDI Spec Hook. Note +// that missing info is filled in when edits are applied by querying the Hook node. +func (d hook) toSpec() *specs.Hook { + s := specs.Hook{ + HookName: d.Lifecycle, + Path: d.Path, + Args: d.Args, + } + return &s +} diff --git a/internal/lookup/executable.go b/internal/lookup/executable.go new file mode 100644 index 00000000..be7577ff --- /dev/null +++ b/internal/lookup/executable.go @@ -0,0 +1,93 @@ +/* +# 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 lookup + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" +) + +const ( + envPath = "PATH" +) + +var defaultPaths = []string{"/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin"} + +type executable struct { + file +} + +// NewExecutaleLocator creates a locator to fine executable files in the path. A logger can also be specified. +func NewExecutaleLocator(logger *log.Logger, root string) Locator { + pathEnv := os.Getenv(envPath) + paths := filepath.SplitList(pathEnv) + + if root != "" { + paths = append(paths, defaultPaths...) + } + + var prefixes []string + for _, dir := range paths { + prefixes = append(prefixes, filepath.Join(root, dir)) + } + l := executable{ + file: file{ + logger: logger, + prefixes: prefixes, + filter: assertExecutable, + }, + } + return &l +} + +var _ Locator = (*executable)(nil) + +// Locate finds executable files in the path. If a relative or absolute path is specified, the prefix paths are not considered. +func (p executable) Locate(filename string) ([]string, error) { + // For absolute paths we ensure that it is executable + if strings.Contains(filename, "/") { + err := assertExecutable(filename) + if err != nil { + return nil, fmt.Errorf("absolute path %v is not an executable file: %v", filename, err) + } + return []string{filename}, nil + } + + return p.file.Locate(filename) +} + +// assertExecutable checks whether the specified path is an execuable file. +func assertExecutable(filename string) error { + err := assertFile(filename) + if err != nil { + return err + } + info, err := os.Stat(filename) + if err != nil { + return err + } + + if info.Mode()&0111 == 0 { + return fmt.Errorf("specified file '%v' is not executable", filename) + } + + return nil +} diff --git a/internal/lookup/file.go b/internal/lookup/file.go new file mode 100644 index 00000000..c9008f93 --- /dev/null +++ b/internal/lookup/file.go @@ -0,0 +1,85 @@ +/* +# 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 lookup + +import ( + "fmt" + "os" + "path/filepath" + + log "github.com/sirupsen/logrus" +) + +// file can be used to locate file (or file-like elements) at a specified set of +// prefixes. The validity of a file is determined by a filter function. +type file struct { + logger *log.Logger + prefixes []string + filter func(string) error +} + +// NewFileLocator creates a Locator that can be used to find files at the specified root. A logger +// can also be specified. +func NewFileLocator(logger *log.Logger, root string) Locator { + l := newFileLocator(logger, root) + + return &l +} + +func newFileLocator(logger *log.Logger, root string) file { + return file{ + logger: logger, + prefixes: []string{root}, + filter: assertFile, + } +} + +var _ Locator = (*file)(nil) + +// Locate attempts to find the specified file. All prefixes are searched and any matching +// candidates are returned. If no matches are found, an error is returned. +func (p file) Locate(filename string) ([]string, error) { + var filenames []string + for _, prefix := range p.prefixes { + candidate := filepath.Join(prefix, filename) + p.logger.Debugf("Checking candidate '%v'", candidate) + err := p.filter(candidate) + if err != nil { + p.logger.Debugf("Candidate '%v' does not meet requirements: %v", candidate, err) + continue + } + filenames = append(filenames, candidate) + } + if len(filename) == 0 { + return nil, fmt.Errorf("file %v not found", filename) + } + return filenames, nil +} + +// assertFile checks whether the specified path is a regular file +func assertFile(filename string) error { + info, err := os.Stat(filename) + if err != nil { + return fmt.Errorf("error getting info for %v: %v", filename, err) + } + + if info.IsDir() { + return fmt.Errorf("specified path '%v' is a directory", filename) + } + + return nil +} diff --git a/internal/lookup/locator.go b/internal/lookup/locator.go new file mode 100644 index 00000000..871e1b02 --- /dev/null +++ b/internal/lookup/locator.go @@ -0,0 +1,24 @@ +/* +# 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 lookup + +//go:generate moq -stub -out locator_mock.go . Locator + +// Locator defines the interface for locating files on a system. +type Locator interface { + Locate(string) ([]string, error) +} diff --git a/internal/lookup/locator_mock.go b/internal/lookup/locator_mock.go new file mode 100644 index 00000000..0c50f345 --- /dev/null +++ b/internal/lookup/locator_mock.go @@ -0,0 +1,77 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package lookup + +import ( + "sync" +) + +// Ensure, that LocatorMock does implement Locator. +// If this is not the case, regenerate this file with moq. +var _ Locator = &LocatorMock{} + +// LocatorMock is a mock implementation of Locator. +// +// func TestSomethingThatUsesLocator(t *testing.T) { +// +// // make and configure a mocked Locator +// mockedLocator := &LocatorMock{ +// LocateFunc: func(s string) ([]string, error) { +// panic("mock out the Locate method") +// }, +// } +// +// // use mockedLocator in code that requires Locator +// // and then make assertions. +// +// } +type LocatorMock struct { + // LocateFunc mocks the Locate method. + LocateFunc func(s string) ([]string, error) + + // calls tracks calls to the methods. + calls struct { + // Locate holds details about calls to the Locate method. + Locate []struct { + // S is the s argument value. + S string + } + } + lockLocate sync.RWMutex +} + +// Locate calls LocateFunc. +func (mock *LocatorMock) Locate(s string) ([]string, error) { + callInfo := struct { + S string + }{ + S: s, + } + mock.lockLocate.Lock() + mock.calls.Locate = append(mock.calls.Locate, callInfo) + mock.lockLocate.Unlock() + if mock.LocateFunc == nil { + var ( + stringsOut []string + errOut error + ) + return stringsOut, errOut + } + return mock.LocateFunc(s) +} + +// LocateCalls gets all the calls that were made to Locate. +// Check the length with: +// len(mockedLocator.LocateCalls()) +func (mock *LocatorMock) LocateCalls() []struct { + S string +} { + var calls []struct { + S string + } + mock.lockLocate.RLock() + calls = mock.calls.Locate + mock.lockLocate.RUnlock() + return calls +} diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 5b0ec14d..67ad3690 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -1,6 +1,6 @@ nvidia-container-toolkit (1.10.0~rc.1-1) experimental; urgency=medium - * Dummy entry + * Add experimental option to NVIDIA Container Runtime -- NVIDIA CORPORATION Thu, 24 Mar 2022 13:22:24 +0200 diff --git a/packaging/rpm/SPECS/nvidia-container-toolkit.spec b/packaging/rpm/SPECS/nvidia-container-toolkit.spec index 72533802..678c3012 100644 --- a/packaging/rpm/SPECS/nvidia-container-toolkit.spec +++ b/packaging/rpm/SPECS/nvidia-container-toolkit.spec @@ -65,7 +65,7 @@ rm -f %{_bindir}/nvidia-container-runtime-hook %changelog * Thu Mar 24 2022 NVIDIA CORPORATION 1.10.0-0.1.rc.1 -- Dummy entry +- Add experimental option to NVIDIA Container Runtime * Fri Mar 18 2022 NVIDIA CORPORATION 1.9.0-1 - [libnvidia-container] Add additional check for Tegra in /sys/.../family file in CLI diff --git a/third_party/libnvidia-container b/third_party/libnvidia-container index a9aa8238..a0c0a43e 160000 --- a/third_party/libnvidia-container +++ b/third_party/libnvidia-container @@ -1 +1 @@ -Subproject commit a9aa8238398d2c1706bd0dbfca48f7690b5e0e70 +Subproject commit a0c0a43e6a6d8c9cc584d15497230a2ecd0d65c7