Skip to content

Commit 5fd2bed

Browse files
committed
Add migration for older configuration versions
Signed-off-by: Derek McGowan <[email protected]>
1 parent 46645b3 commit 5fd2bed

3 files changed

Lines changed: 107 additions & 29 deletions

File tree

services/server/config/config.go

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ import (
4343
// CurrentConfigVersion is the max config version which is supported
4444
const CurrentConfigVersion = 3
4545

46+
// migrations hold the migration functions for every prior containerd config version
47+
var migrations = []func(context.Context, *Config) error{
48+
nil, // Version 0 same as version 1
49+
v1Migrate, // Version 1 plugins renamed to URI for version 2
50+
nil, // Version 2 has only plugin changes from current
51+
}
52+
4653
// NOTE: Any new map fields added also need to be handled in mergeConfig.
4754

4855
// Config provides containerd configuration data for the server
@@ -67,9 +74,11 @@ type Config struct {
6774
Metrics MetricsConfig `toml:"metrics"`
6875
// DisabledPlugins are IDs of plugins to disable. Disabled plugins won't be
6976
// initialized and started.
77+
// DisabledPlugins must use a fully qualified plugin URI.
7078
DisabledPlugins []string `toml:"disabled_plugins"`
7179
// RequiredPlugins are IDs of required plugins. Containerd exits if any
7280
// required plugin doesn't exist or fails to be initialized or started.
81+
// RequiredPlugins must use a fully qualified plugin URI.
7382
RequiredPlugins []string `toml:"required_plugins"`
7483
// Plugins provides plugin specific configuration for the initialization of a plugin
7584
Plugins map[string]interface{} `toml:"plugins"`
@@ -101,41 +110,83 @@ type StreamProcessor struct {
101110
Env []string `toml:"env"`
102111
}
103112

104-
// GetVersion returns the config file's version
105-
func (c *Config) GetVersion() int {
106-
if c.Version == 0 {
107-
return 1
108-
}
109-
return c.Version
110-
}
111-
112113
// ValidateVersion validates the config for a v2 file
113114
func (c *Config) ValidateVersion() error {
114-
version := c.GetVersion()
115-
if version == 1 {
116-
return errors.New("containerd config version `1` is no longer supported since containerd v2.0, please switch to version `2`, " +
117-
"see https://github.com/containerd/containerd/blob/main/docs/PLUGINS.md#version-header")
118-
}
119-
120-
if version > CurrentConfigVersion {
121-
return fmt.Errorf("expected containerd config version equal to or less than `%d`, got `%d`", CurrentConfigVersion, version)
115+
if c.Version > CurrentConfigVersion {
116+
return fmt.Errorf("expected containerd config version equal to or less than `%d`, got `%d`", CurrentConfigVersion, c.Version)
122117
}
123118

124119
for _, p := range c.DisabledPlugins {
125-
if !strings.HasPrefix(p, "io.containerd.") || len(strings.SplitN(p, ".", 4)) < 4 {
120+
if !strings.ContainsAny(p, ".") {
126121
return fmt.Errorf("invalid disabled plugin URI %q expect io.containerd.x.vx", p)
127122
}
128123
}
129124
for _, p := range c.RequiredPlugins {
130-
if !strings.HasPrefix(p, "io.containerd.") || len(strings.SplitN(p, ".", 4)) < 4 {
125+
if !strings.ContainsAny(p, ".") {
131126
return fmt.Errorf("invalid required plugin URI %q expect io.containerd.x.vx", p)
132127
}
133128
}
134-
for p := range c.Plugins {
135-
if !strings.HasPrefix(p, "io.containerd.") || len(strings.SplitN(p, ".", 4)) < 4 {
136-
return fmt.Errorf("invalid plugin key URI %q expect io.containerd.x.vx", p)
129+
130+
return nil
131+
}
132+
133+
// MigrateConfig will convert the config to the latest version before using
134+
func (c *Config) MigrateConfig(ctx context.Context) error {
135+
for c.Version < CurrentConfigVersion {
136+
if m := migrations[c.Version]; m != nil {
137+
if err := m(ctx, c); err != nil {
138+
return err
139+
}
140+
}
141+
c.Version++
142+
}
143+
return nil
144+
}
145+
146+
func v1Migrate(ctx context.Context, c *Config) error {
147+
plugins := make(map[string]interface{}, len(c.Plugins))
148+
149+
// corePlugins is the list of used plugins before v1 was deprecated
150+
corePlugins := map[string]string{
151+
"cri": "io.containerd.grpc.v1.cri",
152+
"cgroups": "io.containerd.monitor.v1.cgroups",
153+
"linux": "io.containerd.runtime.v1.linux",
154+
"scheduler": "io.containerd.gc.v1.scheduler",
155+
"bolt": "io.containerd.metadata.v1.bolt",
156+
"task": "io.containerd.runtime.v2.task",
157+
"opt": "io.containerd.internal.v1.opt",
158+
"restart": "io.containerd.internal.v1.restart",
159+
"tracing": "io.containerd.internal.v1.tracing",
160+
"otlp": "io.containerd.tracing.processor.v1.otlp",
161+
"aufs": "io.containerd.snapshotter.v1.aufs",
162+
"btrfs": "io.containerd.snapshotter.v1.btrfs",
163+
"devmapper": "io.containerd.snapshotter.v1.devmapper",
164+
"native": "io.containerd.snapshotter.v1.native",
165+
"overlayfs": "io.containerd.snapshotter.v1.overlayfs",
166+
"zfs": "io.containerd.snapshotter.v1.zfs",
167+
}
168+
for plugin, value := range c.Plugins {
169+
if !strings.ContainsAny(plugin, ".") {
170+
var ambiguous string
171+
if full, ok := corePlugins[plugin]; ok {
172+
plugin = full
173+
} else if strings.HasSuffix(plugin, "-service") {
174+
plugin = "io.containerd.service.v1." + plugin
175+
} else if plugin == "windows" || plugin == "windows-lcow" {
176+
// runtime, differ, and snapshotter plugins do not have configs for v1
177+
ambiguous = plugin
178+
plugin = "io.containerd.snapshotter.v1." + plugin
179+
} else {
180+
ambiguous = plugin
181+
plugin = "io.containerd.grpc.v1." + plugin
182+
}
183+
if ambiguous != "" {
184+
log.G(ctx).Warnf("Ambiguous %s plugin in v1 config, treating as %s", ambiguous, plugin)
185+
}
137186
}
187+
plugins[plugin] = value
138188
}
189+
c.Plugins = plugins
139190
return nil
140191
}
141192

services/server/config/config_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ import (
2929
"github.com/containerd/log/logtest"
3030
)
3131

32+
func TestMigrations(t *testing.T) {
33+
if len(migrations) != CurrentConfigVersion {
34+
t.Fatalf("Migration missing, expected %d migrations, only %d defined", CurrentConfigVersion, len(migrations))
35+
}
36+
}
37+
3238
func TestMergeConfigs(t *testing.T) {
3339
a := &Config{
3440
Version: 2,

services/server/server.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ func CreateTopLevelDirectories(config *srvconfig.Config) error {
102102

103103
// New creates and initializes a new containerd server
104104
func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
105+
var (
106+
version = config.Version
107+
migrationT time.Duration
108+
)
109+
if version < srvconfig.CurrentConfigVersion {
110+
// Migrate config to latest version
111+
t1 := time.Now()
112+
err := config.MigrateConfig(ctx)
113+
if err != nil {
114+
return nil, err
115+
}
116+
migrationT = time.Since(t1)
117+
}
118+
105119
if err := apply(ctx, config); err != nil {
106120
return nil, err
107121
}
@@ -202,17 +216,24 @@ func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
202216
required[r] = struct{}{}
203217
}
204218

205-
// Run migration for each configuration version
206-
// Run each plugin migration for each version to ensure that migration logic is simple and
207-
// focused on upgrading from one version at a time.
208-
for v := config.Version; v < srvconfig.CurrentConfigVersion; v++ {
209-
for _, p := range plugins {
210-
if p.ConfigMigration != nil {
211-
if err := p.ConfigMigration(ctx, v, config.Plugins); err != nil {
212-
return nil, err
219+
if version < srvconfig.CurrentConfigVersion {
220+
t1 := time.Now()
221+
// Run migration for each configuration version
222+
// Run each plugin migration for each version to ensure that migration logic is simple and
223+
// focused on upgrading from one version at a time.
224+
for v := config.Version; v < srvconfig.CurrentConfigVersion; v++ {
225+
for _, p := range plugins {
226+
if p.ConfigMigration != nil {
227+
if err := p.ConfigMigration(ctx, v, config.Plugins); err != nil {
228+
return nil, err
229+
}
213230
}
214231
}
215232
}
233+
migrationT = migrationT + time.Since(t1)
234+
}
235+
if migrationT > 0 {
236+
log.G(ctx).WithField("t", migrationT).Warnf("Configuration migrated from version %d, use `containerd config migrate` to avoid migration", version)
216237
}
217238

218239
for _, p := range plugins {

0 commit comments

Comments
 (0)