Skip to content

Commit

Permalink
fetch current container runtime config through the command line
Browse files Browse the repository at this point in the history
Signed-off-by: Tariq Ibrahim <[email protected]>

add default runtime binary path to runtimes field of toolkit config toml

Signed-off-by: Tariq Ibrahim <[email protected]>
  • Loading branch information
tariq1890 committed Oct 8, 2024
1 parent 4604e3b commit a3693c8
Show file tree
Hide file tree
Showing 19 changed files with 581 additions and 39 deletions.
72 changes: 58 additions & 14 deletions cmd/nvidia-ctk/runtime/configure/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ const (
defaultContainerdConfigFilePath = "/etc/containerd/config.toml"
defaultCrioConfigFilePath = "/etc/crio/crio.conf"
defaultDockerConfigFilePath = "/etc/docker/daemon.json"

defaultConfigSource = configSourceFile
configSourceCommand = "command"
configSourceFile = "file"

runtimeContainerd = "containerd"
runtimeCrio = "crio"
runtimeDocker = "docker"
)

type command struct {
Expand All @@ -64,6 +72,7 @@ type config struct {
dryRun bool
runtime string
configFilePath string
configSource string
mode string
hookFilePath string

Expand Down Expand Up @@ -120,6 +129,12 @@ func (m command) build() *cli.Command {
Usage: "the config mode for runtimes that support multiple configuration mechanisms",
Destination: &config.mode,
},
&cli.StringFlag{
Name: "config-source",
Usage: "the source to retrieve the container runtime configuration; one of [command, file]\"",
Destination: &config.configSource,
Value: defaultConfigSource,
},
&cli.StringFlag{
Name: "oci-hook-path",
Usage: "the path to the OCI runtime hook to create if --config-mode=oci-hook is specified. If no path is specified, the generated hook is output to STDOUT.\n\tNote: The use of OCI hooks is deprecated.",
Expand Down Expand Up @@ -174,14 +189,14 @@ func (m command) validateFlags(c *cli.Context, config *config) error {
config.mode = "config-file"

switch config.runtime {
case "containerd", "crio", "docker":
case runtimeContainerd, runtimeCrio, runtimeDocker:
break
default:
return fmt.Errorf("unrecognized runtime '%v'", config.runtime)
}

switch config.runtime {
case "containerd", "crio":
case runtimeContainerd, runtimeCrio:
if config.nvidiaRuntime.path == defaultNVIDIARuntimeExecutable {
config.nvidiaRuntime.path = defaultNVIDIARuntimeExpecutablePath
}
Expand All @@ -190,7 +205,7 @@ func (m command) validateFlags(c *cli.Context, config *config) error {
}
}

if config.runtime != "containerd" && config.runtime != "docker" {
if config.runtime != runtimeContainerd && config.runtime != runtimeDocker {
if config.cdi.enabled {
m.logger.Warningf("Ignoring cdi.enabled flag for %v", config.runtime)
}
Expand Down Expand Up @@ -220,22 +235,27 @@ func (m command) configureWrapper(c *cli.Context, config *config) error {
func (m command) configureConfigFile(c *cli.Context, config *config) error {
configFilePath := config.resolveConfigFilePath()

var cfg engine.Interface
var err error
configSource, err := config.resolveConfigSource()
if err != nil {
return err
}

var cfg engine.Interface
switch config.runtime {
case "containerd":
case runtimeContainerd:
cfg, err = containerd.New(
containerd.WithLogger(m.logger),
containerd.WithPath(configFilePath),
containerd.WithConfigSource(toml.FromFile(configFilePath)),
containerd.WithConfigSource(configSource),
)
case "crio":
case runtimeCrio:
cfg, err = crio.New(
crio.WithLogger(m.logger),
crio.WithPath(configFilePath),
crio.WithConfigSource(toml.FromFile(configFilePath)),
crio.WithConfigSource(configSource),
)
case "docker":
case runtimeDocker:
cfg, err = docker.New(
docker.WithLogger(m.logger),
docker.WithPath(configFilePath),
Expand Down Expand Up @@ -285,16 +305,40 @@ func (c *config) resolveConfigFilePath() string {
return c.configFilePath
}
switch c.runtime {
case "containerd":
case runtimeContainerd:
return defaultContainerdConfigFilePath
case "crio":
case runtimeCrio:
return defaultCrioConfigFilePath
case "docker":
case runtimeDocker:
return defaultDockerConfigFilePath
}
return ""
}

// resolveConfigSource returns the default config source or the user provided config source
func (c *config) resolveConfigSource() (toml.Loader, error) {
switch c.configSource {
case configSourceCommand:
cmd, args := c.getConfigSourceCommand()
return toml.FromCommandLine(cmd, args...), nil
case configSourceFile:
return toml.FromFile(c.configFilePath), nil
default:
return nil, fmt.Errorf("unrecognized config source: %s", c.configSource)
}
}

// getConfigSourceCommand returns the default cli command to fetch the current runtime config
func (c *config) getConfigSourceCommand() (string, []string) {
switch c.runtime {
case runtimeContainerd:
return "containerd", []string{"config", "dump"}
case runtimeCrio:
return "crio", []string{"status", "config"}
}
return "", []string{}
}

// getOuputConfigPath returns the configured config path or "" if dry-run is enabled
func (c *config) getOuputConfigPath() string {
if c.dryRun {
Expand All @@ -318,9 +362,9 @@ func enableCDI(config *config, cfg engine.Interface) error {
return nil
}
switch config.runtime {
case "containerd":
case runtimeContainerd:
cfg.Set("enable_cdi", true)
case "docker":
case runtimeDocker:
cfg.Set("features", map[string]bool{"cdi": true})
default:
return fmt.Errorf("enabling CDI in %s is not supported", config.runtime)
Expand Down
15 changes: 15 additions & 0 deletions internal/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,26 @@ func (t *Toml) Get(key string) interface{} {
return (*toml.Tree)(t).Get(key)
}

// GetDefault returns the value for the specified key and falls back to the default value if the Get call fails
func (t *Toml) GetDefault(key string, def interface{}) interface{} {
val := t.Get(key)
if val == nil {
return def
}
return val
}

// Set sets the specified key to the specified value in the TOML config.
func (t *Toml) Set(key string, value interface{}) {
(*toml.Tree)(t).Set(key, value)
}

// WriteTo encode the Tree as Toml and writes it to the writer w.
// Returns the number of bytes written in case of success, or an error if anything happened.
func (t *Toml) WriteTo(w io.Writer) (int64, error) {
return (*toml.Tree)(t).WriteTo(w)
}

// commentDefaults applies the required comments for default values to the Toml.
func (t *Toml) commentDefaults() *Toml {
asToml := (*toml.Tree)(t)
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/engine/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ type Interface interface {
Set(string, interface{})
RemoveRuntime(string) error
Save(string) (int64, error)
GetRuntimeConfig(string) (RuntimeConfig, error)
}

// RuntimeConfig defines the interface to query container runtime handler configuration
type RuntimeConfig interface {
GetBinPath() string
}
14 changes: 12 additions & 2 deletions pkg/config/engine/containerd/config_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ package containerd
import (
"fmt"

"github.com/pelletier/go-toml"

"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/engine"
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
)

// ConfigV1 represents a version 1 containerd config
Expand Down Expand Up @@ -157,3 +156,14 @@ func (c *ConfigV1) Set(key string, value interface{}) {
func (c ConfigV1) Save(path string) (int64, error) {
return (Config)(c).Save(path)
}

func (c *ConfigV1) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
if c == nil || c.Tree == nil {
return nil, fmt.Errorf("config is nil")
}
runtimeData := c.GetSubtreeByPath([]string{"plugins", "cri", "containerd", "runtimes", name})

return &containerdCfgRuntime{
tree: runtimeData,
}, nil
}
96 changes: 96 additions & 0 deletions pkg/config/engine/containerd/config_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,99 @@ func TestAddRuntime(t *testing.T) {
})
}
}

func TestGetRuntimeConfig(t *testing.T) {
logger, _ := testlog.NewNullLogger()
config := `
version = 2
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "nvidia"
disable_snapshot_annotations = true
discard_unpacked_layers = false
ignore_blockio_not_enabled_errors = false
ignore_rdt_not_enabled_errors = false
no_pivot = false
snapshotter = "overlayfs"
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
privileged_without_host_devices_all_devices_allowed = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
sandbox_mode = ""
snapshotter = ""
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
privileged_without_host_devices_all_devices_allowed = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
sandbox_mode = "podsandbox"
snapshotter = ""
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = "/usr/bin/runc"
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = false
`
testCases := []struct {
description string
runtime string
expected string
expectedError error
}{
{
description: "valid runtime config, existing runtime",
runtime: "runc",
expected: "/usr/bin/runc",
expectedError: nil,
},
{
description: "valid runtime config, non-existing runtime",
runtime: "some-other-runtime",
expected: "",
expectedError: nil,
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
cfg, err := toml.Load(config)
require.NoError(t, err)

c := &Config{
Logger: logger,
Tree: cfg,
}
rc, err := c.GetRuntimeConfig(tc.runtime)
require.Equal(t, tc.expectedError, err)
require.Equal(t, tc.expected, rc.GetBinPath())
})
}
}
25 changes: 25 additions & 0 deletions pkg/config/engine/containerd/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ type Config struct {

var _ engine.Interface = (*Config)(nil)

type containerdCfgRuntime struct {
tree *toml.Tree
}

var _ engine.RuntimeConfig = (*containerdCfgRuntime)(nil)

func (c *containerdCfgRuntime) GetBinPath() string {
if c != nil && c.tree != nil {
if binPath, ok := c.tree.GetPath([]string{"options", "BinaryName"}).(string); ok {
return binPath
}
}
return ""
}

// New creates a containerd config with the specified options
func New(opts ...Option) (engine.Interface, error) {
b := &builder{
Expand Down Expand Up @@ -98,3 +113,13 @@ func (c *Config) parseVersion(useLegacyConfig bool) (int, error) {
return -1, fmt.Errorf("unsupported type for version field: %v", v)
}
}

func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
if c == nil || c.Tree == nil {
return nil, fmt.Errorf("config is nil")
}
runtimeData := c.GetSubtreeByPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name})
return &containerdCfgRuntime{
tree: runtimeData,
}, nil
}
11 changes: 11 additions & 0 deletions pkg/config/engine/containerd/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,14 @@ func WithContainerAnnotations(containerAnnotations ...string) Option {
b.containerAnnotations = containerAnnotations
}
}

// CommandLineSource returns the CLI-based containerd config loader
func CommandLineSource(hostRoot string) toml.Loader {
var cliArgs []string
if hostRoot != "" {
cliArgs = append(cliArgs, "chroot", hostRoot)
}
cliArgs = append(cliArgs, "containerd", "config", "dump")

return toml.FromCommandLine(cliArgs[0], cliArgs[1:]...)
}
Loading

0 comments on commit a3693c8

Please sign in to comment.