Skip to content

Commit 2918059

Browse files
alam0rtelezar
authored andcommitted
Add support for containerd version 3 config
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 v3 config also requires a switch to the 'io.containerd.cri.v1.runtime' CRI runtime plugin. See: https://github.com/containerd/containerd/blob/v2.0.0/docs/PLUGINS.md containerd/containerd#10132 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]> Signed-off-by: Christopher Desiniotis <[email protected]>
1 parent 374a72c commit 2918059

File tree

8 files changed

+261
-141
lines changed

8 files changed

+261
-141
lines changed

pkg/config/engine/containerd/config_v2.go renamed to pkg/config/engine/containerd/config.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,40 +30,40 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error {
3030
}
3131
config := *c.Tree
3232

33-
config.Set("version", int64(2))
33+
config.Set("version", c.Version)
3434

3535
runtimeNamesForConfig := engine.GetLowLevelRuntimes(c)
3636
for _, r := range runtimeNamesForConfig {
37-
options := config.GetSubtreeByPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", r})
37+
options := config.GetSubtreeByPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", r})
3838
if options == nil {
3939
continue
4040
}
4141
c.Logger.Debugf("using options from runtime %v: %v", r, options)
42-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}, options.Copy())
42+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name}, options.Copy())
4343
break
4444
}
4545

46-
if config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}) == nil {
46+
if config.GetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name}) == nil {
4747
c.Logger.Warningf("could not infer options from runtimes %v; using defaults", runtimeNamesForConfig)
48-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_type"}, c.RuntimeType)
49-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_root"}, "")
50-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_engine"}, "")
51-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "privileged_without_host_devices"}, false)
48+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "runtime_type"}, c.RuntimeType)
49+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "runtime_root"}, "")
50+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "runtime_engine"}, "")
51+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "privileged_without_host_devices"}, false)
5252
}
5353

5454
if len(c.ContainerAnnotations) > 0 {
55-
annotations, err := c.getRuntimeAnnotations([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "container_annotations"})
55+
annotations, err := c.getRuntimeAnnotations([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "container_annotations"})
5656
if err != nil {
5757
return err
5858
}
5959
annotations = append(c.ContainerAnnotations, annotations...)
60-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "container_annotations"}, annotations)
60+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "container_annotations"}, annotations)
6161
}
6262

63-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "options", "BinaryName"}, path)
63+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "options", "BinaryName"}, path)
6464

6565
if setAsDefault {
66-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}, name)
66+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"}, name)
6767
}
6868

6969
*c.Tree = config
@@ -99,13 +99,13 @@ func (c *Config) getRuntimeAnnotations(path []string) ([]string, error) {
9999
// Set sets the specified containerd option.
100100
func (c *Config) Set(key string, value interface{}) {
101101
config := *c.Tree
102-
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", key}, value)
102+
config.SetPath([]string{"plugins", c.CRIRuntimePluginName, key}, value)
103103
*c.Tree = config
104104
}
105105

106106
// DefaultRuntime returns the default runtime for the cri-o config
107107
func (c Config) DefaultRuntime() string {
108-
if runtime, ok := c.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}).(string); ok {
108+
if runtime, ok := c.GetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"}).(string); ok {
109109
return runtime
110110
}
111111
return ""
@@ -119,14 +119,14 @@ func (c *Config) RemoveRuntime(name string) error {
119119

120120
config := *c.Tree
121121

122-
config.DeletePath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name})
123-
if runtime, ok := config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}).(string); ok {
122+
config.DeletePath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name})
123+
if runtime, ok := config.GetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"}).(string); ok {
124124
if runtime == name {
125-
config.DeletePath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"})
125+
config.DeletePath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"})
126126
}
127127
}
128128

129-
runtimePath := []string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}
129+
runtimePath := []string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name}
130130
for i := 0; i < len(runtimePath); i++ {
131131
if runtimes, ok := config.GetPath(runtimePath[:len(runtimePath)-i]).(*toml.Tree); ok {
132132
if len(runtimes.Keys()) == 0 {

pkg/config/engine/containerd/config_v2_test.go renamed to pkg/config/engine/containerd/config_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,68 @@ func TestAddRuntime(t *testing.T) {
195195
SystemdCgroup = false
196196
`,
197197
},
198+
{
199+
description: "empty v3 spec is supported",
200+
config: `
201+
version = 3
202+
`,
203+
expectedConfig: `
204+
version = 3
205+
[plugins]
206+
[plugins."io.containerd.cri.v1.runtime"]
207+
[plugins."io.containerd.cri.v1.runtime".containerd]
208+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
209+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test]
210+
privileged_without_host_devices = false
211+
runtime_engine = ""
212+
runtime_root = ""
213+
runtime_type = "io.containerd.runc.v2"
214+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test.options]
215+
BinaryName = "/usr/bin/test"
216+
`,
217+
expectedError: nil,
218+
},
219+
{
220+
description: "v3 spec is supported",
221+
config: `
222+
version = 3
223+
[plugins]
224+
[plugins."io.containerd.cri.v1.runtime"]
225+
[plugins."io.containerd.cri.v1.runtime".containerd]
226+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
227+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
228+
privileged_without_host_devices = true
229+
runtime_engine = "engine"
230+
runtime_root = "root"
231+
runtime_type = "type"
232+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options]
233+
BinaryName = "/usr/bin/runc"
234+
SystemdCgroup = true
235+
`,
236+
expectedConfig: `
237+
version = 3
238+
[plugins]
239+
[plugins."io.containerd.cri.v1.runtime"]
240+
[plugins."io.containerd.cri.v1.runtime".containerd]
241+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
242+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
243+
privileged_without_host_devices = true
244+
runtime_engine = "engine"
245+
runtime_root = "root"
246+
runtime_type = "type"
247+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options]
248+
BinaryName = "/usr/bin/runc"
249+
SystemdCgroup = true
250+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test]
251+
privileged_without_host_devices = true
252+
runtime_engine = "engine"
253+
runtime_root = "root"
254+
runtime_type = "type"
255+
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test.options]
256+
BinaryName = "/usr/bin/test"
257+
SystemdCgroup = true
258+
`,
259+
},
198260
}
199261

200262
for _, tc := range testCases {

pkg/config/engine/containerd/containerd.go

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,15 @@ import (
2424
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
2525
)
2626

27+
const (
28+
defaultConfigVersion = 2
29+
defaultRuntimeType = "io.containerd.runc.v2"
30+
)
31+
2732
// Config represents the containerd config
2833
type Config struct {
2934
*toml.Tree
35+
Version int64
3036
Logger logger.Interface
3137
RuntimeType string
3238
ContainerAnnotations []string
@@ -36,6 +42,10 @@ type Config struct {
3642
// This was deprecated in v1.4 in favour of containerd.default_runtime_name.
3743
// Support for this section has been removed in v2.0.
3844
UseLegacyConfig bool
45+
// CRIRuntimePluginName represents the fully qualified name of the containerd plugin
46+
// for the CRI runtime service. The name of this plugin was changed in v3 of the
47+
// containerd configuration file.
48+
CRIRuntimePluginName string
3949
}
4050

4151
var _ engine.Interface = (*Config)(nil)
@@ -60,7 +70,8 @@ func (c *containerdCfgRuntime) GetBinaryPath() string {
6070
// New creates a containerd config with the specified options
6171
func New(opts ...Option) (engine.Interface, error) {
6272
b := &builder{
63-
runtimeType: defaultRuntimeType,
73+
configVersion: defaultConfigVersion,
74+
runtimeType: defaultRuntimeType,
6475
}
6576
for _, opt := range opts {
6677
opt(b)
@@ -77,56 +88,74 @@ func New(opts ...Option) (engine.Interface, error) {
7788
return nil, fmt.Errorf("failed to load config: %v", err)
7889
}
7990

91+
configVersion, err := b.parseVersion(tomlConfig)
92+
if err != nil {
93+
return nil, fmt.Errorf("failed to parse config version: %w", err)
94+
}
95+
b.logger.Infof("Using config version %v", configVersion)
96+
97+
criRuntimePluginName, err := b.criRuntimePluginName(configVersion)
98+
if err != nil {
99+
return nil, fmt.Errorf("failed to get CRI runtime plugin name: %w", err)
100+
}
101+
b.logger.Infof("Using CRI runtime plugin name %q", criRuntimePluginName)
102+
80103
cfg := &Config{
81104
Tree: tomlConfig,
105+
Version: configVersion,
106+
CRIRuntimePluginName: criRuntimePluginName,
82107
Logger: b.logger,
83108
RuntimeType: b.runtimeType,
84-
ContainerAnnotations: b.containerAnnotations,
85109
UseLegacyConfig: b.useLegacyConfig,
110+
ContainerAnnotations: b.containerAnnotations,
86111
}
87112

88-
version, err := cfg.parseVersion()
89-
if err != nil {
90-
return nil, fmt.Errorf("failed to parse config version: %v", err)
91-
}
92-
switch version {
113+
switch configVersion {
93114
case 1:
94115
return (*ConfigV1)(cfg), nil
95-
case 2:
116+
default:
96117
return cfg, nil
97118
}
98-
99-
return nil, fmt.Errorf("unsupported config version: %v", version)
100119
}
101120

102121
// parseVersion returns the version of the config
103-
func (c *Config) parseVersion() (int, error) {
104-
defaultVersion := 2
105-
// For legacy configs, we default to v1 configs.
106-
if c.UseLegacyConfig {
107-
defaultVersion = 1
122+
func (b *builder) parseVersion(c *toml.Tree) (int64, error) {
123+
if c == nil || len(c.Keys()) == 0 {
124+
// No config exists, or the config file is empty.
125+
if b.useLegacyConfig {
126+
// If a legacy config is explicitly requested, we default to a v1 config.
127+
return 1, nil
128+
}
129+
// Use the requested version.
130+
return int64(b.configVersion), nil
108131
}
109132

110133
switch v := c.Get("version").(type) {
111134
case nil:
112-
switch len(c.Keys()) {
113-
case 0: // No config exists, or the config file is empty, use version inferred from containerd
114-
return defaultVersion, nil
115-
default: // A config file exists, has content, and no version is set
116-
return 1, nil
117-
}
135+
return 1, nil
118136
case int64:
119-
return int(v), nil
137+
return v, nil
120138
default:
121139
return -1, fmt.Errorf("unsupported type for version field: %v", v)
122140
}
123141
}
124142

143+
func (b *builder) criRuntimePluginName(configVersion int64) (string, error) {
144+
switch configVersion {
145+
case 1:
146+
return "cri", nil
147+
case 2:
148+
return "io.containerd.grpc.v1.cri", nil
149+
default:
150+
return "io.containerd.cri.v1.runtime", nil
151+
}
152+
}
153+
125154
func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
126155
if c == nil || c.Tree == nil {
127156
return nil, fmt.Errorf("config is nil")
128157
}
129-
runtimeData := c.GetSubtreeByPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name})
158+
runtimeData := c.GetSubtreeByPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name})
130159
return &containerdCfgRuntime{
131160
tree: runtimeData,
132161
}, nil

pkg/config/engine/containerd/option.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,13 @@ import (
2121
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
2222
)
2323

24-
const (
25-
defaultRuntimeType = "io.containerd.runc.v2"
26-
)
27-
2824
type builder struct {
2925
logger logger.Interface
3026
configSource toml.Loader
27+
configVersion int
28+
useLegacyConfig bool
3129
path string
3230
runtimeType string
33-
useLegacyConfig bool
3431
containerAnnotations []string
3532
}
3633

@@ -65,13 +62,20 @@ func WithRuntimeType(runtimeType string) Option {
6562
}
6663
}
6764

68-
// WithUseLegacyConfig sets the useLegacyConfig flag for the config builder
65+
// WithUseLegacyConfig sets the useLegacyConfig flag for the config builder.
6966
func WithUseLegacyConfig(useLegacyConfig bool) Option {
7067
return func(b *builder) {
7168
b.useLegacyConfig = useLegacyConfig
7269
}
7370
}
7471

72+
// WithConfigVersion sets the config version for the config builder
73+
func WithConfigVersion(configVersion int) Option {
74+
return func(b *builder) {
75+
b.configVersion = configVersion
76+
}
77+
}
78+
7579
// WithContainerAnnotations sets the container annotations for the config builder
7680
func WithContainerAnnotations(containerAnnotations ...string) Option {
7781
return func(b *builder) {

pkg/config/toml/source-map.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
# Copyright 2024 NVIDIA CORPORATION
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
**/
16+
17+
package toml
18+
19+
type tomlMap map[string]interface{}
20+
21+
var _ Loader = (*tomlFile)(nil)
22+
23+
// Load loads the contents of the specified TOML file as a map.
24+
func (l tomlMap) Load() (*Tree, error) {
25+
return LoadMap(l)
26+
}

pkg/config/toml/source.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,18 @@ type Loader interface {
2525
Load() (*Tree, error)
2626
}
2727

28+
// FromCommandLine creates a TOML source from the output of a shell command and its corresponding args.
29+
// If the command is empty, an empty config is returned.
30+
func FromCommandLine(cmds ...string) Loader {
31+
if len(cmds) == 0 {
32+
return Empty
33+
}
34+
return &tomlCliSource{
35+
command: cmds[0],
36+
args: cmds[1:],
37+
}
38+
}
39+
2840
// FromFile creates a TOML source from the specified file.
2941
// If an empty string is passed an empty toml config is used.
3042
func FromFile(path string) Loader {
@@ -34,16 +46,13 @@ func FromFile(path string) Loader {
3446
return tomlFile(path)
3547
}
3648

37-
// FromCommandLine creates a TOML source from the output of a shell command and its corresponding args.
38-
// If the command is empty, an empty config is returned.
39-
func FromCommandLine(cmds ...string) Loader {
40-
if len(cmds) == 0 {
49+
// FromMap creates a TOML source for the specified map.
50+
// If an empty map is passed and empty tomly config is used.
51+
func FromMap(m map[string]interface{}) Loader {
52+
if m == nil {
4153
return Empty
4254
}
43-
return &tomlCliSource{
44-
command: cmds[0],
45-
args: cmds[1:],
46-
}
55+
return tomlMap(m)
4756
}
4857

4958
// FromString creates a TOML source for the specified contents.

0 commit comments

Comments
 (0)