nvidia-container-toolkit/tools/container/containerd/config_v2_test.go
Evan Lezar 62d88e7c95 Add cdi and legacy mode runtimes
This change adds .cdi and .legacy mode-specific runtimes the list of
runtimes supported by the operator. These are also installed as
part of the NVIDIA Container Toolkit.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2023-03-07 20:59:55 +02:00

362 lines
9.8 KiB
Go

/**
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
#
# 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 main
import (
"fmt"
"testing"
"github.com/NVIDIA/nvidia-container-toolkit/internal/config/engine/containerd"
"github.com/pelletier/go-toml"
"github.com/stretchr/testify/require"
)
const (
runtimeType = "runtime_type"
)
func TestUpdateV2ConfigDefaultRuntime(t *testing.T) {
const runtimeDir = "/test/runtime/dir"
testCases := []struct {
setAsDefault bool
runtimeClass string
expectedDefaultRuntimeName interface{}
}{
{},
{
setAsDefault: false,
runtimeClass: "nvidia",
expectedDefaultRuntimeName: nil,
},
{
setAsDefault: false,
runtimeClass: "NAME",
expectedDefaultRuntimeName: nil,
},
{
setAsDefault: false,
runtimeClass: "nvidia-experimental",
expectedDefaultRuntimeName: nil,
},
{
setAsDefault: true,
runtimeClass: "nvidia",
expectedDefaultRuntimeName: "nvidia",
},
{
setAsDefault: true,
runtimeClass: "NAME",
expectedDefaultRuntimeName: "NAME",
},
{
setAsDefault: true,
runtimeClass: "nvidia-experimental",
expectedDefaultRuntimeName: "nvidia-experimental",
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
o := &options{
setAsDefault: tc.setAsDefault,
runtimeClass: tc.runtimeClass,
runtimeDir: runtimeDir,
}
config, err := toml.TreeFromMap(map[string]interface{}{})
require.NoError(t, err, "%d: %v", i, tc)
v2 := &containerd.Config{
Tree: config,
RuntimeType: runtimeType,
}
err = UpdateConfig(v2, o)
require.NoError(t, err, "%d: %v", i, tc)
defaultRuntimeName := config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"})
require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName, "%d: %v", i, tc)
})
}
}
func TestUpdateV2Config(t *testing.T) {
const runtimeDir = "/test/runtime/dir"
const expectedVersion = int64(2)
expectedBinaries := []string{
"/test/runtime/dir/nvidia-container-runtime",
"/test/runtime/dir/nvidia-container-runtime.experimental",
"/test/runtime/dir/nvidia-container-runtime.cdi",
"/test/runtime/dir/nvidia-container-runtime.legacy",
}
testCases := []struct {
runtimeClass string
expectedRuntimes []string
}{
{
runtimeClass: "nvidia",
expectedRuntimes: []string{"nvidia", "nvidia-experimental", "nvidia-cdi", "nvidia-legacy"},
},
{
runtimeClass: "NAME",
expectedRuntimes: []string{"NAME", "nvidia-experimental", "nvidia-cdi", "nvidia-legacy"},
},
{
runtimeClass: "nvidia-experimental",
expectedRuntimes: []string{"nvidia", "nvidia-experimental", "nvidia-cdi", "nvidia-legacy"},
},
}
for i, tc := range testCases {
o := &options{
runtimeClass: tc.runtimeClass,
runtimeType: runtimeType,
runtimeDir: runtimeDir,
}
config, err := toml.TreeFromMap(map[string]interface{}{})
require.NoError(t, err, "%d: %v", i, tc)
v2 := &containerd.Config{
Tree: config,
RuntimeType: runtimeType,
}
err = UpdateConfig(v2, o)
require.NoError(t, err, "%d: %v", i, tc)
version, ok := config.Get("version").(int64)
require.True(t, ok)
require.EqualValues(t, expectedVersion, version, "%d: %v", i, tc)
runtimes, ok := config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes"}).(*toml.Tree)
require.True(t, ok)
runtimeClasses := runtimes.Keys()
require.ElementsMatch(t, tc.expectedRuntimes, runtimeClasses, "%d: %v", i, tc)
for i, r := range tc.expectedRuntimes {
runtimeConfig := runtimes.Get(r)
expected, err := runtimeTomlConfigV2(expectedBinaries[i])
require.NoError(t, err, "%d: %v", i, tc)
configContents, _ := toml.Marshal(runtimeConfig)
expectedContents, _ := toml.Marshal(expected)
require.Equal(t, string(expectedContents), string(configContents), "%d: %v: %v", i, r, tc)
}
}
}
func TestUpdateV2ConfigWithRuncPresent(t *testing.T) {
const runcBinary = "/runc-binary"
const runtimeDir = "/test/runtime/dir"
const expectedVersion = int64(2)
expectedBinaries := []string{
runcBinary,
"/test/runtime/dir/nvidia-container-runtime",
"/test/runtime/dir/nvidia-container-runtime.experimental",
"/test/runtime/dir/nvidia-container-runtime.cdi",
"/test/runtime/dir/nvidia-container-runtime.legacy",
}
testCases := []struct {
runtimeClass string
expectedRuntimes []string
}{
{
runtimeClass: "nvidia",
expectedRuntimes: []string{"runc", "nvidia", "nvidia-experimental", "nvidia-cdi", "nvidia-legacy"},
},
{
runtimeClass: "NAME",
expectedRuntimes: []string{"runc", "NAME", "nvidia-experimental", "nvidia-cdi", "nvidia-legacy"},
},
{
runtimeClass: "nvidia-experimental",
expectedRuntimes: []string{"runc", "nvidia", "nvidia-experimental", "nvidia-cdi", "nvidia-legacy"},
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
o := &options{
runtimeClass: tc.runtimeClass,
runtimeType: runtimeType,
runtimeDir: runtimeDir,
}
config, err := toml.TreeFromMap(runcConfigMapV2("/runc-binary"))
require.NoError(t, err, "%d: %v", i, tc)
v2 := &containerd.Config{
Tree: config,
RuntimeType: runtimeType,
}
err = UpdateConfig(v2, o)
require.NoError(t, err, "%d: %v", i, tc)
version, ok := v2.Get("version").(int64)
require.True(t, ok)
require.EqualValues(t, expectedVersion, version)
runtimes, ok := v2.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes"}).(*toml.Tree)
require.True(t, ok, "%d: %v", i, tc)
runtimeClasses := runtimes.Keys()
require.ElementsMatch(t, tc.expectedRuntimes, runtimeClasses, "%d: %v", i, tc)
for i, r := range tc.expectedRuntimes {
runtimeConfig := runtimes.Get(r)
expected, err := toml.TreeFromMap(runcRuntimeConfigMapV2(expectedBinaries[i]))
require.NoError(t, err, "%d: %v", i, tc)
configContents, _ := toml.Marshal(runtimeConfig)
expectedContents, _ := toml.Marshal(expected)
require.Equal(t, string(expectedContents), string(configContents), "%d: %v: %v", i, r, tc)
}
})
}
}
func TestRevertV2Config(t *testing.T) {
testCases := []struct {
config map[string]interface {
}
expected map[string]interface{}
}{
{},
{
config: map[string]interface{}{
"version": int64(2),
},
},
{
config: map[string]interface{}{
"version": int64(2),
"plugins": map[string]interface{}{
"io.containerd.grpc.v1.cri": map[string]interface{}{
"containerd": map[string]interface{}{
"runtimes": map[string]interface{}{
"nvidia": runtimeMapV2("/test/runtime/dir/nvidia-container-runtime"),
"nvidia-experimental": runtimeMapV2("/test/runtime/dir/nvidia-container-runtime.experimental"),
},
},
},
},
},
},
{
config: map[string]interface{}{
"version": int64(2),
"plugins": map[string]interface{}{
"io.containerd.grpc.v1.cri": map[string]interface{}{
"containerd": map[string]interface{}{
"runtimes": map[string]interface{}{
"nvidia": runtimeMapV2("/test/runtime/dir/nvidia-container-runtime"),
"nvidia-experimental": runtimeMapV2("/test/runtime/dir/nvidia-container-runtime.experimental"),
},
"default_runtime_name": "nvidia",
},
},
},
},
},
}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
o := &options{
runtimeClass: "nvidia",
}
config, err := toml.TreeFromMap(tc.config)
require.NoError(t, err, "%d: %v", i, tc)
expected, err := toml.TreeFromMap(tc.expected)
require.NoError(t, err, "%d: %v", i, tc)
v2 := &containerd.Config{
Tree: config,
RuntimeType: runtimeType,
}
err = RevertConfig(v2, o)
require.NoError(t, err, "%d: %v", i, tc)
configContents, _ := toml.Marshal(config)
expectedContents, _ := toml.Marshal(expected)
require.Equal(t, string(expectedContents), string(configContents), "%d: %v", i, tc)
})
}
}
func runtimeTomlConfigV2(binary string) (*toml.Tree, error) {
return toml.TreeFromMap(runtimeMapV2(binary))
}
func runtimeMapV2(binary string) map[string]interface{} {
return map[string]interface{}{
"runtime_type": runtimeType,
"runtime_root": "",
"runtime_engine": "",
"privileged_without_host_devices": false,
"options": map[string]interface{}{
"BinaryName": binary,
},
}
}
func runcConfigMapV2(binary string) map[string]interface{} {
return map[string]interface{}{
"plugins": map[string]interface{}{
"io.containerd.grpc.v1.cri": map[string]interface{}{
"containerd": map[string]interface{}{
"runtimes": map[string]interface{}{
"runc": runcRuntimeConfigMapV2(binary),
},
},
},
},
}
}
func runcRuntimeConfigMapV2(binary string) map[string]interface{} {
return map[string]interface{}{
"runtime_type": "runc_runtime_type",
"runtime_root": "runc_runtime_root",
"runtime_engine": "runc_runtime_engine",
"privileged_without_host_devices": true,
"options": map[string]interface{}{
"runc-option": "value",
"BinaryName": binary,
},
}
}