mirror of
https://github.com/opencontainers/runc.git
synced 2025-10-04 07:06:39 +08:00

Sometimes debug.bats test cases are failing like this: > not ok 27 global --debug to --log --log-format 'json' > # (in test file tests/integration/debug.bats, line 77) > # `[[ "${output}" == *"child process in init()"* ]]' failed It happens more when writing to disk. This issue is caused by the fact that runc spawns log forwarding goroutine (ForwardLogs) but does not wait for it to finish, resulting in missing debug lines from nsexec. ForwardLogs itself, though, never finishes, because it reads from a reading side of a pipe which writing side is not closed. This is especially true in case of runc create, which spawns runc init and exits; meanwhile runc init waits on exec fifo for arbitrarily long time before doing execve. So, to fix the failure described above, we need to: 1. Make runc create/run/exec wait for ForwardLogs to finish; 2. Make runc init close its log pipe file descriptor (i.e. the one which value is passed in _LIBCONTAINER_LOGPIPE environment variable). This is exactly what this commit does: 1. Amend ForwardLogs to return a channel, and wait for it in start(). 2. In runc init, save the log fd and close it as late as possible. PS I have to admit I still do not understand why an explicit close of log pipe fd is required in e.g. (*linuxSetnsInit).Init, right before the execve which (thanks to CLOEXEC) closes the fd anyway. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
404 lines
9.2 KiB
Go
404 lines
9.2 KiB
Go
// +build linux
|
|
|
|
package libcontainer
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
"github.com/opencontainers/runc/libcontainer/configs"
|
|
"github.com/opencontainers/runc/libcontainer/intelrdt"
|
|
"github.com/opencontainers/runc/libcontainer/system"
|
|
)
|
|
|
|
type mockCgroupManager struct {
|
|
pids []int
|
|
allPids []int
|
|
stats *cgroups.Stats
|
|
paths map[string]string
|
|
}
|
|
|
|
type mockIntelRdtManager struct {
|
|
stats *intelrdt.Stats
|
|
path string
|
|
}
|
|
|
|
func (m *mockCgroupManager) GetPids() ([]int, error) {
|
|
return m.pids, nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) GetAllPids() ([]int, error) {
|
|
return m.allPids, nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) GetStats() (*cgroups.Stats, error) {
|
|
return m.stats, nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) Apply(pid int) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) Set(container *configs.Config) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) Destroy() error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) Exists() bool {
|
|
_, err := os.Lstat(m.Path("devices"))
|
|
return err == nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) OOMKillCount() (uint64, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) GetPaths() map[string]string {
|
|
return m.paths
|
|
}
|
|
|
|
func (m *mockCgroupManager) Path(subsys string) string {
|
|
return m.paths[subsys]
|
|
}
|
|
|
|
func (m *mockCgroupManager) Freeze(state configs.FreezerState) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) GetCgroups() (*configs.Cgroup, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockCgroupManager) GetFreezerState() (configs.FreezerState, error) {
|
|
return configs.Thawed, nil
|
|
}
|
|
|
|
func (m *mockIntelRdtManager) Apply(pid int) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockIntelRdtManager) GetStats() (*intelrdt.Stats, error) {
|
|
return m.stats, nil
|
|
}
|
|
|
|
func (m *mockIntelRdtManager) Destroy() error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockIntelRdtManager) GetPath() string {
|
|
return m.path
|
|
}
|
|
|
|
func (m *mockIntelRdtManager) Set(container *configs.Config) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockIntelRdtManager) GetCgroups() (*configs.Cgroup, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
type mockProcess struct {
|
|
_pid int
|
|
started uint64
|
|
}
|
|
|
|
func (m *mockProcess) terminate() error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockProcess) pid() int {
|
|
return m._pid
|
|
}
|
|
|
|
func (m *mockProcess) startTime() (uint64, error) {
|
|
return m.started, nil
|
|
}
|
|
|
|
func (m *mockProcess) start() error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockProcess) wait() (*os.ProcessState, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *mockProcess) signal(_ os.Signal) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockProcess) externalDescriptors() []string {
|
|
return []string{}
|
|
}
|
|
|
|
func (m *mockProcess) setExternalDescriptors(newFds []string) {
|
|
}
|
|
|
|
func (m *mockProcess) forwardChildLogs() chan error {
|
|
return nil
|
|
}
|
|
|
|
func TestGetContainerPids(t *testing.T) {
|
|
pid := 1
|
|
stat, err := system.Stat(pid)
|
|
if err != nil {
|
|
t.Fatalf("can't stat pid %d, got %v", pid, err)
|
|
}
|
|
container := &linuxContainer{
|
|
id: "myid",
|
|
config: &configs.Config{},
|
|
cgroupManager: &mockCgroupManager{
|
|
allPids: []int{1, 2, 3},
|
|
paths: map[string]string{
|
|
"device": "/proc/self/cgroups",
|
|
},
|
|
},
|
|
initProcess: &mockProcess{
|
|
_pid: 1,
|
|
started: 10,
|
|
},
|
|
initProcessStartTime: stat.StartTime,
|
|
}
|
|
container.state = &runningState{c: container}
|
|
pids, err := container.Processes()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, expected := range []int{1, 2, 3} {
|
|
if pids[i] != expected {
|
|
t.Fatalf("expected pid %d but received %d", expected, pids[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetContainerStats(t *testing.T) {
|
|
container := &linuxContainer{
|
|
id: "myid",
|
|
config: &configs.Config{},
|
|
cgroupManager: &mockCgroupManager{
|
|
pids: []int{1, 2, 3},
|
|
stats: &cgroups.Stats{
|
|
MemoryStats: cgroups.MemoryStats{
|
|
Usage: cgroups.MemoryData{
|
|
Usage: 1024,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
intelRdtManager: &mockIntelRdtManager{
|
|
stats: &intelrdt.Stats{
|
|
L3CacheSchema: "L3:0=f;1=f0",
|
|
MemBwSchema: "MB:0=20;1=70",
|
|
},
|
|
},
|
|
}
|
|
stats, err := container.Stats()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stats.CgroupStats == nil {
|
|
t.Fatal("cgroup stats are nil")
|
|
}
|
|
if stats.CgroupStats.MemoryStats.Usage.Usage != 1024 {
|
|
t.Fatalf("expected memory usage 1024 but received %d", stats.CgroupStats.MemoryStats.Usage.Usage)
|
|
}
|
|
if intelrdt.IsCATEnabled() {
|
|
if stats.IntelRdtStats == nil {
|
|
t.Fatal("intel rdt stats are nil")
|
|
}
|
|
if stats.IntelRdtStats.L3CacheSchema != "L3:0=f;1=f0" {
|
|
t.Fatalf("expected L3CacheSchema L3:0=f;1=f0 but received %s", stats.IntelRdtStats.L3CacheSchema)
|
|
}
|
|
}
|
|
if intelrdt.IsMBAEnabled() {
|
|
if stats.IntelRdtStats == nil {
|
|
t.Fatal("intel rdt stats are nil")
|
|
}
|
|
if stats.IntelRdtStats.MemBwSchema != "MB:0=20;1=70" {
|
|
t.Fatalf("expected MemBwSchema MB:0=20;1=70 but received %s", stats.IntelRdtStats.MemBwSchema)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetContainerState(t *testing.T) {
|
|
var (
|
|
pid = os.Getpid()
|
|
expectedMemoryPath = "/sys/fs/cgroup/memory/myid"
|
|
expectedNetworkPath = fmt.Sprintf("/proc/%d/ns/net", pid)
|
|
expectedIntelRdtPath = "/sys/fs/resctrl/myid"
|
|
)
|
|
container := &linuxContainer{
|
|
id: "myid",
|
|
config: &configs.Config{
|
|
Namespaces: []configs.Namespace{
|
|
{Type: configs.NEWPID},
|
|
{Type: configs.NEWNS},
|
|
{Type: configs.NEWNET, Path: expectedNetworkPath},
|
|
{Type: configs.NEWUTS},
|
|
// emulate host for IPC
|
|
//{Type: configs.NEWIPC},
|
|
{Type: configs.NEWCGROUP},
|
|
},
|
|
},
|
|
initProcess: &mockProcess{
|
|
_pid: pid,
|
|
started: 10,
|
|
},
|
|
cgroupManager: &mockCgroupManager{
|
|
pids: []int{1, 2, 3},
|
|
stats: &cgroups.Stats{
|
|
MemoryStats: cgroups.MemoryStats{
|
|
Usage: cgroups.MemoryData{
|
|
Usage: 1024,
|
|
},
|
|
},
|
|
},
|
|
paths: map[string]string{
|
|
"memory": expectedMemoryPath,
|
|
},
|
|
},
|
|
intelRdtManager: &mockIntelRdtManager{
|
|
stats: &intelrdt.Stats{
|
|
L3CacheSchema: "L3:0=f0;1=f",
|
|
MemBwSchema: "MB:0=70;1=20",
|
|
},
|
|
path: expectedIntelRdtPath,
|
|
},
|
|
}
|
|
container.state = &createdState{c: container}
|
|
state, err := container.State()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state.InitProcessPid != pid {
|
|
t.Fatalf("expected pid %d but received %d", pid, state.InitProcessPid)
|
|
}
|
|
if state.InitProcessStartTime != 10 {
|
|
t.Fatalf("expected process start time 10 but received %d", state.InitProcessStartTime)
|
|
}
|
|
paths := state.CgroupPaths
|
|
if paths == nil {
|
|
t.Fatal("cgroup paths should not be nil")
|
|
}
|
|
if memPath := paths["memory"]; memPath != expectedMemoryPath {
|
|
t.Fatalf("expected memory path %q but received %q", expectedMemoryPath, memPath)
|
|
}
|
|
if intelrdt.IsCATEnabled() || intelrdt.IsMBAEnabled() {
|
|
intelRdtPath := state.IntelRdtPath
|
|
if intelRdtPath == "" {
|
|
t.Fatal("intel rdt path should not be empty")
|
|
}
|
|
if intelRdtPath != expectedIntelRdtPath {
|
|
t.Fatalf("expected intel rdt path %q but received %q", expectedIntelRdtPath, intelRdtPath)
|
|
}
|
|
}
|
|
for _, ns := range container.config.Namespaces {
|
|
path := state.NamespacePaths[ns.Type]
|
|
if path == "" {
|
|
t.Fatalf("expected non nil namespace path for %s", ns.Type)
|
|
}
|
|
if ns.Type == configs.NEWNET {
|
|
if path != expectedNetworkPath {
|
|
t.Fatalf("expected path %q but received %q", expectedNetworkPath, path)
|
|
}
|
|
} else {
|
|
file := ""
|
|
switch ns.Type {
|
|
case configs.NEWNET:
|
|
file = "net"
|
|
case configs.NEWNS:
|
|
file = "mnt"
|
|
case configs.NEWPID:
|
|
file = "pid"
|
|
case configs.NEWIPC:
|
|
file = "ipc"
|
|
case configs.NEWUSER:
|
|
file = "user"
|
|
case configs.NEWUTS:
|
|
file = "uts"
|
|
case configs.NEWCGROUP:
|
|
file = "cgroup"
|
|
}
|
|
expected := fmt.Sprintf("/proc/%d/ns/%s", pid, file)
|
|
if expected != path {
|
|
t.Fatalf("expected path %q but received %q", expected, path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetContainerStateAfterUpdate(t *testing.T) {
|
|
var (
|
|
pid = os.Getpid()
|
|
)
|
|
stat, err := system.Stat(pid)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rootDir, err := ioutil.TempDir("", "TestGetContainerStateAfterUpdate")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(rootDir)
|
|
|
|
container := &linuxContainer{
|
|
root: rootDir,
|
|
id: "myid",
|
|
config: &configs.Config{
|
|
Namespaces: []configs.Namespace{
|
|
{Type: configs.NEWPID},
|
|
{Type: configs.NEWNS},
|
|
{Type: configs.NEWNET},
|
|
{Type: configs.NEWUTS},
|
|
{Type: configs.NEWIPC},
|
|
},
|
|
Cgroups: &configs.Cgroup{
|
|
Resources: &configs.Resources{
|
|
Memory: 1024,
|
|
},
|
|
},
|
|
},
|
|
initProcess: &mockProcess{
|
|
_pid: pid,
|
|
started: stat.StartTime,
|
|
},
|
|
cgroupManager: &mockCgroupManager{},
|
|
}
|
|
container.state = &createdState{c: container}
|
|
state, err := container.State()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state.InitProcessPid != pid {
|
|
t.Fatalf("expected pid %d but received %d", pid, state.InitProcessPid)
|
|
}
|
|
if state.InitProcessStartTime != stat.StartTime {
|
|
t.Fatalf("expected process start time %d but received %d", stat.StartTime, state.InitProcessStartTime)
|
|
}
|
|
if state.Config.Cgroups.Resources.Memory != 1024 {
|
|
t.Fatalf("expected Memory to be 1024 but received %q", state.Config.Cgroups.Memory)
|
|
}
|
|
|
|
// Set initProcessStartTime so we fake to be running
|
|
container.initProcessStartTime = state.InitProcessStartTime
|
|
container.state = &runningState{c: container}
|
|
newConfig := container.Config()
|
|
newConfig.Cgroups.Resources.Memory = 2048
|
|
if err := container.Set(newConfig); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
state, err = container.State()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state.Config.Cgroups.Resources.Memory != 2048 {
|
|
t.Fatalf("expected Memory to be 2048 but received %q", state.Config.Cgroups.Memory)
|
|
}
|
|
}
|