| //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)) | |
| } | |
| }) | |
| } | |
| } |