mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-16 20:40:55 +08:00

Some systemd properties are documented as having "Sec" suffix (e.g. "TimeoutStopSec") but are expected to have "USec" suffix when passed over dbus, so let's provide appropriate conversion to improve compatibility. This means, one can specify TimeoutStopSec with a numeric argument, in seconds, and it will be properly converted to TimeoutStopUsec with the argument in microseconds. As a side bonus, even float values are converted, so e.g. TimeoutStopSec=1.5 is possible. This turned out a bit more tricky to implement when I was originally expected, since there are a handful of numeric types in dbus and each one requires explicit conversion. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
588 lines
14 KiB
Go
588 lines
14 KiB
Go
// +build linux
|
|
|
|
package specconv
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/godbus/dbus"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/configs/validate"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
func TestCreateCommandHookTimeout(t *testing.T) {
|
|
timeout := 3600
|
|
hook := specs.Hook{
|
|
Path: "/some/hook/path",
|
|
Args: []string{"--some", "thing"},
|
|
Env: []string{"SOME=value"},
|
|
Timeout: &timeout,
|
|
}
|
|
command := createCommandHook(hook)
|
|
timeoutStr := command.Timeout.String()
|
|
if timeoutStr != "1h0m0s" {
|
|
t.Errorf("Expected the Timeout to be 1h0m0s, got: %s", timeoutStr)
|
|
}
|
|
}
|
|
|
|
func TestCreateHooks(t *testing.T) {
|
|
rspec := &specs.Spec{
|
|
Hooks: &specs.Hooks{
|
|
Prestart: []specs.Hook{
|
|
{
|
|
Path: "/some/hook/path",
|
|
},
|
|
{
|
|
Path: "/some/hook2/path",
|
|
Args: []string{"--some", "thing"},
|
|
},
|
|
},
|
|
Poststart: []specs.Hook{
|
|
{
|
|
Path: "/some/hook/path",
|
|
Args: []string{"--some", "thing"},
|
|
Env: []string{"SOME=value"},
|
|
},
|
|
{
|
|
Path: "/some/hook2/path",
|
|
},
|
|
{
|
|
Path: "/some/hook3/path",
|
|
},
|
|
},
|
|
Poststop: []specs.Hook{
|
|
{
|
|
Path: "/some/hook/path",
|
|
Args: []string{"--some", "thing"},
|
|
Env: []string{"SOME=value"},
|
|
},
|
|
{
|
|
Path: "/some/hook2/path",
|
|
},
|
|
{
|
|
Path: "/some/hook3/path",
|
|
},
|
|
{
|
|
Path: "/some/hook4/path",
|
|
Args: []string{"--some", "thing"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
conf := &configs.Config{}
|
|
createHooks(rspec, conf)
|
|
|
|
prestart := conf.Hooks.Prestart
|
|
|
|
if len(prestart) != 2 {
|
|
t.Error("Expected 2 Prestart hooks")
|
|
}
|
|
|
|
poststart := conf.Hooks.Poststart
|
|
|
|
if len(poststart) != 3 {
|
|
t.Error("Expected 3 Poststart hooks")
|
|
}
|
|
|
|
poststop := conf.Hooks.Poststop
|
|
|
|
if len(poststop) != 4 {
|
|
t.Error("Expected 4 Poststop hooks")
|
|
}
|
|
|
|
}
|
|
func TestSetupSeccomp(t *testing.T) {
|
|
conf := &specs.LinuxSeccomp{
|
|
DefaultAction: "SCMP_ACT_ERRNO",
|
|
Architectures: []specs.Arch{specs.ArchX86_64, specs.ArchARM},
|
|
Syscalls: []specs.LinuxSyscall{
|
|
{
|
|
Names: []string{"clone"},
|
|
Action: "SCMP_ACT_ALLOW",
|
|
Args: []specs.LinuxSeccompArg{
|
|
{
|
|
Index: 0,
|
|
Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP,
|
|
ValueTwo: 0,
|
|
Op: "SCMP_CMP_MASKED_EQ",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Names: []string{
|
|
"select",
|
|
"semctl",
|
|
"semget",
|
|
"semop",
|
|
"semtimedop",
|
|
"send",
|
|
"sendfile",
|
|
},
|
|
Action: "SCMP_ACT_ALLOW",
|
|
},
|
|
},
|
|
}
|
|
seccomp, err := SetupSeccomp(conf)
|
|
|
|
if err != nil {
|
|
t.Errorf("Couldn't create Seccomp config: %v", err)
|
|
}
|
|
|
|
if seccomp.DefaultAction != 2 { // SCMP_ACT_ERRNO
|
|
t.Error("Wrong conversion for DefaultAction")
|
|
}
|
|
|
|
if len(seccomp.Architectures) != 2 {
|
|
t.Error("Wrong number of architectures")
|
|
}
|
|
|
|
if seccomp.Architectures[0] != "amd64" || seccomp.Architectures[1] != "arm" {
|
|
t.Error("Expected architectures are not found")
|
|
}
|
|
|
|
calls := seccomp.Syscalls
|
|
|
|
callsLength := len(calls)
|
|
if callsLength != 8 {
|
|
t.Errorf("Expected 8 syscalls, got :%d", callsLength)
|
|
}
|
|
|
|
for i, call := range calls {
|
|
if i == 0 {
|
|
expectedCloneSyscallArgs := configs.Arg{
|
|
Index: 0,
|
|
Op: 7, // SCMP_CMP_MASKED_EQ
|
|
Value: unix.CLONE_NEWNS | unix.CLONE_NEWUTS | unix.CLONE_NEWIPC | unix.CLONE_NEWUSER | unix.CLONE_NEWPID | unix.CLONE_NEWNET | unix.CLONE_NEWCGROUP,
|
|
ValueTwo: 0,
|
|
}
|
|
if expectedCloneSyscallArgs != *call.Args[0] {
|
|
t.Errorf("Wrong arguments conversion for the clone syscall under test")
|
|
}
|
|
}
|
|
if call.Action != 4 {
|
|
t.Error("Wrong conversion for the clone syscall action")
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func TestLinuxCgroupWithMemoryResource(t *testing.T) {
|
|
cgroupsPath := "/user/cgroups/path/id"
|
|
|
|
spec := &specs.Spec{}
|
|
devices := []specs.LinuxDeviceCgroup{
|
|
{
|
|
Allow: false,
|
|
Access: "rwm",
|
|
},
|
|
}
|
|
|
|
limit := int64(100)
|
|
reservation := int64(50)
|
|
swap := int64(20)
|
|
kernel := int64(40)
|
|
kernelTCP := int64(45)
|
|
swappiness := uint64(1)
|
|
swappinessPtr := &swappiness
|
|
disableOOMKiller := true
|
|
resources := &specs.LinuxResources{
|
|
Devices: devices,
|
|
Memory: &specs.LinuxMemory{
|
|
Limit: &limit,
|
|
Reservation: &reservation,
|
|
Swap: &swap,
|
|
Kernel: &kernel,
|
|
KernelTCP: &kernelTCP,
|
|
Swappiness: swappinessPtr,
|
|
DisableOOMKiller: &disableOOMKiller,
|
|
},
|
|
}
|
|
spec.Linux = &specs.Linux{
|
|
CgroupsPath: cgroupsPath,
|
|
Resources: resources,
|
|
}
|
|
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: false,
|
|
Spec: spec,
|
|
}
|
|
|
|
cgroup, err := CreateCgroupConfig(opts)
|
|
if err != nil {
|
|
t.Errorf("Couldn't create Cgroup config: %v", err)
|
|
}
|
|
|
|
if cgroup.Path != cgroupsPath {
|
|
t.Errorf("Wrong cgroupsPath, expected '%s' got '%s'", cgroupsPath, cgroup.Path)
|
|
}
|
|
if cgroup.Resources.Memory != limit {
|
|
t.Errorf("Expected to have %d as memory limit, got %d", limit, cgroup.Resources.Memory)
|
|
}
|
|
if cgroup.Resources.MemoryReservation != reservation {
|
|
t.Errorf("Expected to have %d as memory reservation, got %d", reservation, cgroup.Resources.MemoryReservation)
|
|
}
|
|
if cgroup.Resources.MemorySwap != swap {
|
|
t.Errorf("Expected to have %d as swap, got %d", swap, cgroup.Resources.MemorySwap)
|
|
}
|
|
if cgroup.Resources.KernelMemory != kernel {
|
|
t.Errorf("Expected to have %d as Kernel Memory, got %d", kernel, cgroup.Resources.KernelMemory)
|
|
}
|
|
if cgroup.Resources.KernelMemoryTCP != kernelTCP {
|
|
t.Errorf("Expected to have %d as TCP Kernel Memory, got %d", kernelTCP, cgroup.Resources.KernelMemoryTCP)
|
|
}
|
|
if cgroup.Resources.MemorySwappiness != swappinessPtr {
|
|
t.Errorf("Expected to have %d as memory swappiness, got %d", swappinessPtr, cgroup.Resources.MemorySwappiness)
|
|
}
|
|
if cgroup.Resources.OomKillDisable != disableOOMKiller {
|
|
t.Errorf("The OOMKiller should be enabled")
|
|
}
|
|
}
|
|
|
|
func TestLinuxCgroupSystemd(t *testing.T) {
|
|
cgroupsPath := "parent:scopeprefix:name"
|
|
|
|
spec := &specs.Spec{}
|
|
spec.Linux = &specs.Linux{
|
|
CgroupsPath: cgroupsPath,
|
|
}
|
|
|
|
opts := &CreateOpts{
|
|
UseSystemdCgroup: true,
|
|
Spec: spec,
|
|
}
|
|
|
|
cgroup, err := CreateCgroupConfig(opts)
|
|
|
|
if err != nil {
|
|
t.Errorf("Couldn't create Cgroup config: %v", err)
|
|
}
|
|
|
|
expectedParent := "parent"
|
|
if cgroup.Parent != expectedParent {
|
|
t.Errorf("Expected to have %s as Parent instead of %s", expectedParent, cgroup.Parent)
|
|
}
|
|
|
|
expectedScopePrefix := "scopeprefix"
|
|
if cgroup.ScopePrefix != expectedScopePrefix {
|
|
t.Errorf("Expected to have %s as ScopePrefix instead of %s", expectedScopePrefix, cgroup.ScopePrefix)
|
|
}
|
|
|
|
expectedName := "name"
|
|
if cgroup.Name != expectedName {
|
|
t.Errorf("Expected to have %s as Name instead of %s", expectedName, cgroup.Name)
|
|
}
|
|
}
|
|
|
|
func TestLinuxCgroupSystemdWithEmptyPath(t *testing.T) {
|
|
cgroupsPath := ""
|
|
|
|
spec := &specs.Spec{}
|
|
spec.Linux = &specs.Linux{
|
|
CgroupsPath: cgroupsPath,
|
|
}
|
|
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: true,
|
|
Spec: spec,
|
|
}
|
|
|
|
cgroup, err := CreateCgroupConfig(opts)
|
|
|
|
if err != nil {
|
|
t.Errorf("Couldn't create Cgroup config: %v", err)
|
|
}
|
|
|
|
expectedParent := "system.slice"
|
|
if cgroup.Parent != expectedParent {
|
|
t.Errorf("Expected to have %s as Parent instead of %s", expectedParent, cgroup.Parent)
|
|
}
|
|
|
|
expectedScopePrefix := "runc"
|
|
if cgroup.ScopePrefix != expectedScopePrefix {
|
|
t.Errorf("Expected to have %s as ScopePrefix instead of %s", expectedScopePrefix, cgroup.ScopePrefix)
|
|
}
|
|
|
|
if cgroup.Name != opts.CgroupName {
|
|
t.Errorf("Expected to have %s as Name instead of %s", opts.CgroupName, cgroup.Name)
|
|
}
|
|
}
|
|
|
|
func TestLinuxCgroupSystemdWithInvalidPath(t *testing.T) {
|
|
cgroupsPath := "/user/cgroups/path/id"
|
|
|
|
spec := &specs.Spec{}
|
|
spec.Linux = &specs.Linux{
|
|
CgroupsPath: cgroupsPath,
|
|
}
|
|
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: true,
|
|
Spec: spec,
|
|
}
|
|
|
|
_, err := CreateCgroupConfig(opts)
|
|
if err == nil {
|
|
t.Error("Expected to produce an error if not using the correct format for cgroup paths belonging to systemd")
|
|
}
|
|
}
|
|
func TestLinuxCgroupsPathSpecified(t *testing.T) {
|
|
cgroupsPath := "/user/cgroups/path/id"
|
|
|
|
spec := &specs.Spec{}
|
|
spec.Linux = &specs.Linux{
|
|
CgroupsPath: cgroupsPath,
|
|
}
|
|
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: false,
|
|
Spec: spec,
|
|
}
|
|
|
|
cgroup, err := CreateCgroupConfig(opts)
|
|
if err != nil {
|
|
t.Errorf("Couldn't create Cgroup config: %v", err)
|
|
}
|
|
|
|
if cgroup.Path != cgroupsPath {
|
|
t.Errorf("Wrong cgroupsPath, expected '%s' got '%s'", cgroupsPath, cgroup.Path)
|
|
}
|
|
}
|
|
|
|
func TestLinuxCgroupsPathNotSpecified(t *testing.T) {
|
|
spec := &specs.Spec{}
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: false,
|
|
Spec: spec,
|
|
}
|
|
|
|
cgroup, err := CreateCgroupConfig(opts)
|
|
if err != nil {
|
|
t.Errorf("Couldn't create Cgroup config: %v", err)
|
|
}
|
|
|
|
if cgroup.Path != "" {
|
|
t.Errorf("Wrong cgroupsPath, expected it to be empty string, got '%s'", cgroup.Path)
|
|
}
|
|
}
|
|
|
|
func TestSpecconvExampleValidate(t *testing.T) {
|
|
spec := Example()
|
|
spec.Root.Path = "/"
|
|
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: false,
|
|
Spec: spec,
|
|
}
|
|
|
|
config, err := CreateLibcontainerConfig(opts)
|
|
if err != nil {
|
|
t.Errorf("Couldn't create libcontainer config: %v", err)
|
|
}
|
|
|
|
validator := validate.New()
|
|
if err := validator.Validate(config); err != nil {
|
|
t.Errorf("Expected specconv to produce valid container config: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDupNamespaces(t *testing.T) {
|
|
spec := &specs.Spec{
|
|
Root: &specs.Root{
|
|
Path: "rootfs",
|
|
},
|
|
Linux: &specs.Linux{
|
|
Namespaces: []specs.LinuxNamespace{
|
|
{
|
|
Type: "pid",
|
|
},
|
|
{
|
|
Type: "pid",
|
|
Path: "/proc/1/ns/pid",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
_, err := CreateLibcontainerConfig(&CreateOpts{
|
|
Spec: spec,
|
|
})
|
|
|
|
if !strings.Contains(err.Error(), "malformed spec file: duplicated ns") {
|
|
t.Errorf("Duplicated namespaces should be forbidden")
|
|
}
|
|
}
|
|
|
|
func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) {
|
|
if _, err := os.Stat("/proc/self/ns/user"); os.IsNotExist(err) {
|
|
t.Skip("userns is unsupported")
|
|
}
|
|
|
|
spec := Example()
|
|
spec.Root.Path = "/"
|
|
ToRootless(spec)
|
|
|
|
opts := &CreateOpts{
|
|
CgroupName: "ContainerID",
|
|
UseSystemdCgroup: false,
|
|
Spec: spec,
|
|
RootlessEUID: true,
|
|
RootlessCgroups: true,
|
|
}
|
|
|
|
config, err := CreateLibcontainerConfig(opts)
|
|
if err != nil {
|
|
t.Errorf("Couldn't create libcontainer config: %v", err)
|
|
}
|
|
|
|
validator := validate.New()
|
|
if err := validator.Validate(config); err != nil {
|
|
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestInitSystemdProps(t *testing.T) {
|
|
type inT struct {
|
|
name, value string
|
|
}
|
|
type expT struct {
|
|
isErr bool
|
|
name string
|
|
value interface{}
|
|
}
|
|
|
|
testCases := []struct {
|
|
desc string
|
|
in inT
|
|
exp expT
|
|
}{
|
|
{
|
|
in: inT{"org.systemd.property.TimeoutStopUSec", "uint64 123456789"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(123456789)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (default numeric type)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "456"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(456000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (byte)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "byte 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (int16)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "int16 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (uint16)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "uint16 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (int32)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "int32 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (uint32)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "uint32 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (int64)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "int64 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (uint64)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "uint64 234"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234000000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (float)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "234.789"},
|
|
exp: expT{false, "TimeoutStopUSec", uint64(234789000)},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (bool -- invalid value)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "false"},
|
|
exp: expT{true, "", ""},
|
|
},
|
|
{
|
|
desc: "convert USec to Sec (string -- invalid value)",
|
|
in: inT{"org.systemd.property.TimeoutStopSec", "'covfefe'"},
|
|
exp: expT{true, "", ""},
|
|
},
|
|
{
|
|
in: inT{"org.systemd.property.CollectMode", "'inactive-or-failed'"},
|
|
exp: expT{false, "CollectMode", "inactive-or-failed"},
|
|
},
|
|
{
|
|
desc: "unrelated property",
|
|
in: inT{"some.other.annotation", "0"},
|
|
exp: expT{false, "", ""},
|
|
},
|
|
{
|
|
desc: "too short property name",
|
|
in: inT{"org.systemd.property.Xo", "1"},
|
|
exp: expT{true, "", ""},
|
|
},
|
|
{
|
|
desc: "invalid character in property name",
|
|
in: inT{"org.systemd.property.Number1", "1"},
|
|
exp: expT{true, "", ""},
|
|
},
|
|
{
|
|
desc: "invalid property value",
|
|
in: inT{"org.systemd.property.ValidName", "invalid-value"},
|
|
exp: expT{true, "", ""},
|
|
},
|
|
}
|
|
|
|
spec := &specs.Spec{}
|
|
|
|
for _, tc := range testCases {
|
|
tc := tc
|
|
spec.Annotations = map[string]string{tc.in.name: tc.in.value}
|
|
|
|
outMap, err := initSystemdProps(spec)
|
|
//t.Logf("input %+v, expected %+v, got err:%v out:%+v", tc.in, tc.exp, err, outMap)
|
|
|
|
if tc.exp.isErr != (err != nil) {
|
|
t.Errorf("input %+v, expecting error: %v, got %v", tc.in, tc.exp.isErr, err)
|
|
}
|
|
expLen := 1 // expect a single item
|
|
if tc.exp.name == "" {
|
|
expLen = 0 // expect nothing
|
|
}
|
|
if len(outMap) != expLen {
|
|
t.Fatalf("input %+v, expected %d, got %d entries: %v", tc.in, expLen, len(outMap), outMap)
|
|
}
|
|
if expLen == 0 {
|
|
continue
|
|
}
|
|
|
|
out := outMap[0]
|
|
if tc.exp.name != out.Name {
|
|
t.Errorf("input %+v, expecting name: %q, got %q", tc.in, tc.exp.name, out.Name)
|
|
}
|
|
expValue := dbus.MakeVariant(tc.exp.value).String()
|
|
if expValue != out.Value.String() {
|
|
t.Errorf("input %+v, expecting value: %s, got %s", tc.in, expValue, out.Value)
|
|
}
|
|
}
|
|
}
|