b0eed5ade6
Kubernetes only permits RuntimeClass values which are valid lowercase RFC 1123 labels, which disallows the period character. This prevents cri-dockerd from being able to support configuring alternative shimv2 runtimes for a pod as shimv2 runtime names must contain at least one period character. Add support for configuring named shimv2 runtimes in daemon.json so that runtime names can be aliased to Kubernetes-compatible names. Allow options to be set on shimv2 runtimes in daemon.json. The names of the new daemon runtime config fields have been selected to correspond with the equivalent field names in cri-containerd's configuration so that users can more easily follow documentation from the runtime vendor written for cri-containerd and apply it to daemon.json. Signed-off-by: Cory Snider <csnider@mirantis.com>
238 lines
6.4 KiB
Go
238 lines
6.4 KiB
Go
//go:build !windows
|
|
// +build !windows
|
|
|
|
package daemon
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/containerd/containerd/plugin"
|
|
v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
|
|
"gotest.tools/v3/assert"
|
|
is "gotest.tools/v3/assert/cmp"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/daemon/config"
|
|
"github.com/docker/docker/errdefs"
|
|
)
|
|
|
|
func TestInitRuntimes_InvalidConfigs(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
runtime types.Runtime
|
|
expectErr string
|
|
}{
|
|
{
|
|
name: "Empty",
|
|
expectErr: "either a runtimeType or a path must be configured",
|
|
},
|
|
{
|
|
name: "ArgsOnly",
|
|
runtime: types.Runtime{Args: []string{"foo", "bar"}},
|
|
expectErr: "either a runtimeType or a path must be configured",
|
|
},
|
|
{
|
|
name: "OptionsOnly",
|
|
runtime: types.Runtime{Options: map[string]interface{}{"hello": "world"}},
|
|
expectErr: "either a runtimeType or a path must be configured",
|
|
},
|
|
{
|
|
name: "PathAndType",
|
|
runtime: types.Runtime{Path: "/bin/true", Type: "io.containerd.runsc.v1"},
|
|
expectErr: "cannot configure both",
|
|
},
|
|
{
|
|
name: "PathAndOptions",
|
|
runtime: types.Runtime{Path: "/bin/true", Options: map[string]interface{}{"a": "b"}},
|
|
expectErr: "options cannot be used with a path runtime",
|
|
},
|
|
{
|
|
name: "TypeAndArgs",
|
|
runtime: types.Runtime{Type: "io.containerd.runsc.v1", Args: []string{"--version"}},
|
|
expectErr: "args cannot be used with a runtimeType runtime",
|
|
},
|
|
{
|
|
name: "PathArgsOptions",
|
|
runtime: types.Runtime{
|
|
Path: "/bin/true",
|
|
Args: []string{"--version"},
|
|
Options: map[string]interface{}{"hmm": 3},
|
|
},
|
|
expectErr: "options cannot be used with a path runtime",
|
|
},
|
|
{
|
|
name: "TypeOptionsArgs",
|
|
runtime: types.Runtime{
|
|
Type: "io.containerd.kata.v2",
|
|
Options: map[string]interface{}{"a": "b"},
|
|
Args: []string{"--help"},
|
|
},
|
|
expectErr: "args cannot be used with a runtimeType runtime",
|
|
},
|
|
{
|
|
name: "PathArgsTypeOptions",
|
|
runtime: types.Runtime{
|
|
Path: "/bin/true",
|
|
Args: []string{"foo"},
|
|
Type: "io.containerd.runsc.v1",
|
|
Options: map[string]interface{}{"a": "b"},
|
|
},
|
|
expectErr: "cannot configure both",
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cfg, err := config.New()
|
|
assert.NilError(t, err)
|
|
d := &Daemon{configStore: cfg}
|
|
d.configStore.Root = t.TempDir()
|
|
assert.Assert(t, os.Mkdir(filepath.Join(d.configStore.Root, "runtimes"), 0700))
|
|
|
|
err = d.initRuntimes(map[string]types.Runtime{"myruntime": tt.runtime})
|
|
assert.Check(t, is.ErrorContains(err, tt.expectErr))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRuntime(t *testing.T) {
|
|
// Configured runtimes can have any arbitrary name, including names
|
|
// which would not be allowed as implicit runtime names. Explicit takes
|
|
// precedence over implicit.
|
|
const configuredRtName = "my/custom.runtime.v1"
|
|
configuredRuntime := types.Runtime{Path: "/bin/true"}
|
|
|
|
const rtWithArgsName = "withargs"
|
|
rtWithArgs := types.Runtime{
|
|
Path: "/bin/false",
|
|
Args: []string{"--version"},
|
|
}
|
|
|
|
const shimWithOptsName = "shimwithopts"
|
|
shimWithOpts := types.Runtime{
|
|
Type: plugin.RuntimeRuncV2,
|
|
Options: map[string]interface{}{"IoUid": 42},
|
|
}
|
|
|
|
const shimAliasName = "wasmedge"
|
|
shimAlias := types.Runtime{Type: "io.containerd.wasmedge.v1"}
|
|
|
|
const configuredShimByPathName = "shimwithpath"
|
|
configuredShimByPath := types.Runtime{Type: "/path/to/my/shim"}
|
|
|
|
cfg, err := config.New()
|
|
assert.NilError(t, err)
|
|
|
|
d := &Daemon{configStore: cfg}
|
|
d.configStore.Root = t.TempDir()
|
|
assert.Assert(t, os.Mkdir(filepath.Join(d.configStore.Root, "runtimes"), 0700))
|
|
d.configStore.Runtimes = map[string]types.Runtime{
|
|
configuredRtName: configuredRuntime,
|
|
rtWithArgsName: rtWithArgs,
|
|
shimWithOptsName: shimWithOpts,
|
|
shimAliasName: shimAlias,
|
|
configuredShimByPathName: configuredShimByPath,
|
|
}
|
|
configureRuntimes(d.configStore)
|
|
assert.Assert(t, d.loadRuntimes())
|
|
|
|
stockRuntime, ok := d.configStore.Runtimes[config.StockRuntimeName]
|
|
assert.Assert(t, ok, "stock runtime could not be found (test needs to be updated)")
|
|
|
|
configdOpts := *stockRuntime.ShimConfig.Opts.(*v2runcoptions.Options)
|
|
configdOpts.BinaryName = configuredRuntime.Path
|
|
|
|
for _, tt := range []struct {
|
|
name, runtime string
|
|
wantShim string
|
|
wantOpts interface{}
|
|
}{
|
|
{
|
|
name: "StockRuntime",
|
|
runtime: config.StockRuntimeName,
|
|
wantShim: stockRuntime.ShimConfig.Binary,
|
|
wantOpts: stockRuntime.ShimConfig.Opts,
|
|
},
|
|
{
|
|
name: "ShimName",
|
|
runtime: "io.containerd.my-shim.v42",
|
|
wantShim: "io.containerd.my-shim.v42",
|
|
},
|
|
{
|
|
// containerd is pretty loose about the format of runtime names. Perhaps too
|
|
// loose. The only requirements are that the name contain a dot and (depending
|
|
// on the containerd version) not start with a dot. It does not enforce any
|
|
// particular format of the dot-delimited components of the name.
|
|
name: "VersionlessShimName",
|
|
runtime: "io.containerd.my-shim",
|
|
wantShim: "io.containerd.my-shim",
|
|
},
|
|
{
|
|
name: "IllformedShimName",
|
|
runtime: "myshim",
|
|
},
|
|
{
|
|
name: "EmptyString",
|
|
runtime: "",
|
|
},
|
|
{
|
|
name: "PathToShim",
|
|
runtime: "/path/to/runc",
|
|
},
|
|
{
|
|
name: "PathToShimName",
|
|
runtime: "/path/to/io.containerd.runc.v2",
|
|
},
|
|
{
|
|
name: "RelPathToShim",
|
|
runtime: "my/io.containerd.runc.v2",
|
|
},
|
|
{
|
|
name: "ConfiguredRuntime",
|
|
runtime: configuredRtName,
|
|
wantShim: stockRuntime.ShimConfig.Binary,
|
|
wantOpts: &configdOpts,
|
|
},
|
|
{
|
|
name: "RuntimeWithArgs",
|
|
runtime: rtWithArgsName,
|
|
wantShim: stockRuntime.ShimConfig.Binary,
|
|
wantOpts: defaultV2ShimConfig(
|
|
d.configStore,
|
|
d.rewriteRuntimePath(
|
|
rtWithArgsName,
|
|
rtWithArgs.Path,
|
|
rtWithArgs.Args)).Opts,
|
|
},
|
|
{
|
|
name: "ShimWithOpts",
|
|
runtime: shimWithOptsName,
|
|
wantShim: shimWithOpts.Type,
|
|
wantOpts: &v2runcoptions.Options{IoUid: 42},
|
|
},
|
|
{
|
|
name: "ShimAlias",
|
|
runtime: shimAliasName,
|
|
wantShim: shimAlias.Type,
|
|
},
|
|
{
|
|
name: "ConfiguredShimByPath",
|
|
runtime: configuredShimByPathName,
|
|
wantShim: configuredShimByPath.Type,
|
|
},
|
|
} {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotShim, gotOpts, err := d.getRuntime(tt.runtime)
|
|
assert.Check(t, is.Equal(gotShim, tt.wantShim))
|
|
assert.Check(t, is.DeepEqual(gotOpts, tt.wantOpts))
|
|
if tt.wantShim != "" {
|
|
assert.Check(t, err)
|
|
} else {
|
|
assert.Check(t, errdefs.IsInvalidParameter(err))
|
|
}
|
|
})
|
|
}
|
|
}
|