Skip to content

Commit

Permalink
Add support for containerd version 3 config
Browse files Browse the repository at this point in the history
This change adds support for containerd configs with version=3.
From the perspective of the runtime configuration the contents of the
config are the same. This means that we just have to load the new
version and ensure that this is propagated to the generated config.

Note that we still use a default config of version=2 since we need to
ensure compatibility with older containerd versions (1.6.x and 1.7.x).

Signed-off-by: Sam Lockart <[email protected]>
Signed-off-by: Evan Lezar <[email protected]>
  • Loading branch information
alam0rt authored and elezar committed Nov 29, 2024
1 parent 1dd66d0 commit 5d073fb
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 123 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error {
}
config := *c.Tree

config.Set("version", int64(2))
config.Set("version", c.Version)

runtimeNamesForConfig := engine.GetLowLevelRuntimes(c)
for _, r := range runtimeNamesForConfig {
Expand Down
62 changes: 62 additions & 0 deletions pkg/config/engine/containerd/config_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,68 @@ func TestAddRuntime(t *testing.T) {
SystemdCgroup = false
`,
},
{
description: "empty v3 spec is supported",
config: `
version = 3
`,
expectedConfig: `
version = 3
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test]
privileged_without_host_devices = false
runtime_engine = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test.options]
BinaryName = "/usr/bin/test"
`,
expectedError: nil,
},
{
description: "v3 spec is supported",
config: `
version = 3
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
privileged_without_host_devices = true
runtime_engine = "engine"
runtime_root = "root"
runtime_type = "type"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = "/usr/bin/runc"
SystemdCgroup = true
`,
expectedConfig: `
version = 3
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
[plugins."io.containerd.grpc.v1.cri".containerd]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
privileged_without_host_devices = true
runtime_engine = "engine"
runtime_root = "root"
runtime_type = "type"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = "/usr/bin/runc"
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test]
privileged_without_host_devices = true
runtime_engine = "engine"
runtime_root = "root"
runtime_type = "type"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test.options]
BinaryName = "/usr/bin/test"
SystemdCgroup = true
`,
},
}

for _, tc := range testCases {
Expand Down
51 changes: 29 additions & 22 deletions pkg/config/engine/containerd/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ import (
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
)

const (
defaultConfigVersion = 2
defaultRuntimeType = "io.containerd.runc.v2"
)

// Config represents the containerd config
type Config struct {
*toml.Tree
Version int64
Logger logger.Interface
RuntimeType string
ContainerAnnotations []string
Expand Down Expand Up @@ -60,7 +66,8 @@ func (c *containerdCfgRuntime) GetBinaryPath() string {
// New creates a containerd config with the specified options
func New(opts ...Option) (engine.Interface, error) {
b := &builder{
runtimeType: defaultRuntimeType,
configVersion: defaultConfigVersion,
runtimeType: defaultRuntimeType,
}
for _, opt := range opts {
opt(b)
Expand All @@ -77,46 +84,46 @@ func New(opts ...Option) (engine.Interface, error) {
return nil, fmt.Errorf("failed to load config: %v", err)
}

configVersion, err := b.parseVersion(tomlConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse config version: %w", err)
}

cfg := &Config{
Tree: tomlConfig,
Version: configVersion,
Logger: b.logger,
RuntimeType: b.runtimeType,
ContainerAnnotations: b.containerAnnotations,
UseLegacyConfig: b.useLegacyConfig,
ContainerAnnotations: b.containerAnnotations,
}

version, err := cfg.parseVersion()
if err != nil {
return nil, fmt.Errorf("failed to parse config version: %v", err)
}
switch version {
switch configVersion {
case 1:
return (*ConfigV1)(cfg), nil
case 2:
case 2, 3:
return cfg, nil
}

return nil, fmt.Errorf("unsupported config version: %v", version)
return nil, fmt.Errorf("unsupported config version: %v", configVersion)
}

// parseVersion returns the version of the config
func (c *Config) parseVersion() (int, error) {
defaultVersion := 2
// For legacy configs, we default to v1 configs.
if c.UseLegacyConfig {
defaultVersion = 1
func (b *builder) parseVersion(c *toml.Tree) (int64, error) {
if c == nil || len(c.Keys()) == 0 {
// No config exists, or the config file is empty.
if b.useLegacyConfig {
// If a legacy config is explicitly requested, we default to a v1 config.
return 1, nil
}
// Use the requested version.
return int64(b.configVersion), nil
}

switch v := c.Get("version").(type) {
case nil:
switch len(c.Keys()) {
case 0: // No config exists, or the config file is empty, use version inferred from containerd
return defaultVersion, nil
default: // A config file exists, has content, and no version is set
return 1, nil
}
return 1, nil
case int64:
return int(v), nil
return v, nil
default:
return -1, fmt.Errorf("unsupported type for version field: %v", v)
}
Expand Down
16 changes: 10 additions & 6 deletions pkg/config/engine/containerd/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@ import (
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
)

const (
defaultRuntimeType = "io.containerd.runc.v2"
)

type builder struct {
logger logger.Interface
configSource toml.Loader
configVersion int
useLegacyConfig bool
path string
runtimeType string
useLegacyConfig bool
containerAnnotations []string
}

Expand Down Expand Up @@ -65,13 +62,20 @@ func WithRuntimeType(runtimeType string) Option {
}
}

// WithUseLegacyConfig sets the useLegacyConfig flag for the config builder
// WithUseLegacyConfig sets the useLegacyConfig flag for the config builder.
func WithUseLegacyConfig(useLegacyConfig bool) Option {
return func(b *builder) {
b.useLegacyConfig = useLegacyConfig
}
}

// WithConfigVersion sets the config version for the config builder
func WithConfigVersion(configVersion int) Option {
return func(b *builder) {
b.configVersion = configVersion
}
}

// WithContainerAnnotations sets the container annotations for the config builder
func WithContainerAnnotations(containerAnnotations ...string) Option {
return func(b *builder) {
Expand Down
26 changes: 26 additions & 0 deletions pkg/config/toml/source-map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 toml

type tomlMap map[string]interface{}

var _ Loader = (*tomlFile)(nil)

// Load loads the contents of the specified TOML file as a map.
func (l tomlMap) Load() (*Tree, error) {
return LoadMap(l)
}
25 changes: 17 additions & 8 deletions pkg/config/toml/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ type Loader interface {
Load() (*Tree, error)
}

// FromCommandLine creates a TOML source from the output of a shell command and its corresponding args.
// If the command is empty, an empty config is returned.
func FromCommandLine(cmds ...string) Loader {
if len(cmds) == 0 {
return Empty
}
return &tomlCliSource{
command: cmds[0],
args: cmds[1:],
}
}

// FromFile creates a TOML source from the specified file.
// If an empty string is passed an empty toml config is used.
func FromFile(path string) Loader {
Expand All @@ -34,16 +46,13 @@ func FromFile(path string) Loader {
return tomlFile(path)
}

// FromCommandLine creates a TOML source from the output of a shell command and its corresponding args.
// If the command is empty, an empty config is returned.
func FromCommandLine(cmds ...string) Loader {
if len(cmds) == 0 {
// FromMap creates a TOML source for the specified map.
// If an empty map is passed and empty tomly config is used.
func FromMap(m map[string]interface{}) Loader {
if m == nil {
return Empty
}
return &tomlCliSource{
command: cmds[0],
args: cmds[1:],
}
return tomlMap(m)
}

// FromString creates a TOML source for the specified contents.
Expand Down
Loading

0 comments on commit 5d073fb

Please sign in to comment.