
On Linux, the snapshot size differs depending on the backing filesystem. See issue 7909. Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
425 lines
13 KiB
Go
425 lines
13 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
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 integration
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
goruntime "runtime"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/integration/images"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
|
|
)
|
|
|
|
// Test to verify for a container ID
|
|
func TestContainerStats(t *testing.T) {
|
|
t.Logf("Create a pod config and run sandbox container")
|
|
sb, sbConfig := PodSandboxConfigWithCleanup(t, "sandbox1", "stats")
|
|
|
|
pauseImage := images.Get(images.Pause)
|
|
EnsureImageExists(t, pauseImage)
|
|
|
|
t.Logf("Create a container config and run container in a pod")
|
|
containerConfig := ContainerConfig(
|
|
"container1",
|
|
pauseImage,
|
|
WithTestLabels(),
|
|
WithTestAnnotations(),
|
|
)
|
|
cn, err := runtimeService.CreateContainer(sb, containerConfig, sbConfig)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.RemoveContainer(cn))
|
|
}()
|
|
require.NoError(t, runtimeService.StartContainer(cn))
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.StopContainer(cn, 10))
|
|
}()
|
|
|
|
t.Logf("Fetch stats for container")
|
|
var s *runtime.ContainerStats
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
s, err = runtimeService.ContainerStats(cn)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if s.GetWritableLayer().GetTimestamp() != 0 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}, time.Second, 30*time.Second))
|
|
|
|
t.Logf("Verify stats received for container %q", cn)
|
|
testStats(t, s, containerConfig)
|
|
}
|
|
|
|
// Test to verify if the consumed stats are correct.
|
|
func TestContainerConsumedStats(t *testing.T) {
|
|
t.Logf("Create a pod config and run sandbox container")
|
|
sb, sbConfig := PodSandboxConfigWithCleanup(t, "sandbox1", "stats")
|
|
|
|
testImage := images.Get(images.ResourceConsumer)
|
|
EnsureImageExists(t, testImage)
|
|
|
|
t.Logf("Create a container config and run container in a pod")
|
|
containerConfig := ContainerConfig(
|
|
"container1",
|
|
testImage,
|
|
WithTestLabels(),
|
|
WithTestAnnotations(),
|
|
)
|
|
cn, err := runtimeService.CreateContainer(sb, containerConfig, sbConfig)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.RemoveContainer(cn))
|
|
}()
|
|
require.NoError(t, runtimeService.StartContainer(cn))
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.StopContainer(cn, 10))
|
|
}()
|
|
|
|
t.Logf("Fetch initial stats for container")
|
|
var s *runtime.ContainerStats
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
s, err = runtimeService.ContainerStats(cn)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if s.GetWritableLayer().GetTimestamp() > 0 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}, time.Second, 30*time.Second))
|
|
|
|
initialMemory := s.GetMemory().GetWorkingSetBytes().GetValue()
|
|
t.Logf("Initial container memory consumption is %f MB. Consume 100 MB and expect the reported stats to increase accordingly", float64(initialMemory)/(1024*1024))
|
|
|
|
// consume 100 MB memory for 30 seconds.
|
|
var command []string
|
|
if goruntime.GOOS == "windows" {
|
|
// -d: Leak and touch memory in specified MBs
|
|
// -c: Count of number of objects to allocate
|
|
command = []string{"testlimit.exe", "-accepteula", "-d", "25", "-c", "4"}
|
|
} else {
|
|
command = []string{"stress", "-m", "1", "--vm-bytes", "100M", "--vm-hang", "0", "-t", "30"}
|
|
}
|
|
|
|
go func() {
|
|
_, _, err = runtimeService.ExecSync(cn, command, 30*time.Second)
|
|
}()
|
|
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
s, err = runtimeService.ContainerStats(cn)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if s.GetMemory().GetWorkingSetBytes().GetValue() > initialMemory+100*1024*1024 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}, time.Second, 30*time.Second))
|
|
}
|
|
|
|
// Test to verify filtering without any filter
|
|
func TestContainerListStats(t *testing.T) {
|
|
var (
|
|
stats []*runtime.ContainerStats
|
|
err error
|
|
)
|
|
t.Logf("Create a pod config and run sandbox container")
|
|
sb, sbConfig := PodSandboxConfigWithCleanup(t, "running-pod", "statsls")
|
|
|
|
pauseImage := images.Get(images.Pause)
|
|
EnsureImageExists(t, pauseImage)
|
|
|
|
t.Logf("Create a container config and run containers in a pod")
|
|
containerConfigMap := make(map[string]*runtime.ContainerConfig)
|
|
for i := 0; i < 3; i++ {
|
|
cName := fmt.Sprintf("container%d", i)
|
|
containerConfig := ContainerConfig(
|
|
cName,
|
|
pauseImage,
|
|
WithTestLabels(),
|
|
WithTestAnnotations(),
|
|
)
|
|
cn, err := runtimeService.CreateContainer(sb, containerConfig, sbConfig)
|
|
require.NoError(t, err)
|
|
containerConfigMap[cn] = containerConfig
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.RemoveContainer(cn))
|
|
}()
|
|
require.NoError(t, runtimeService.StartContainer(cn))
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.StopContainer(cn, 10))
|
|
}()
|
|
}
|
|
|
|
t.Logf("Fetch all container stats")
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
stats, err = runtimeService.ListContainerStats(&runtime.ContainerStatsFilter{})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, s := range stats {
|
|
if s.GetWritableLayer().GetTimestamp() == 0 {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}, time.Second, 30*time.Second))
|
|
|
|
t.Logf("Verify all container stats")
|
|
for _, s := range stats {
|
|
testStats(t, s, containerConfigMap[s.GetAttributes().GetId()])
|
|
}
|
|
}
|
|
|
|
// Test to verify filtering given a specific container ID
|
|
// TODO Convert the filter tests into table driven tests and unit tests
|
|
func TestContainerListStatsWithIdFilter(t *testing.T) {
|
|
var (
|
|
stats []*runtime.ContainerStats
|
|
err error
|
|
)
|
|
t.Logf("Create a pod config and run sandbox container")
|
|
sb, sbConfig := PodSandboxConfigWithCleanup(t, "running-pod", "statsls")
|
|
|
|
pauseImage := images.Get(images.Pause)
|
|
EnsureImageExists(t, pauseImage)
|
|
|
|
t.Logf("Create a container config and run containers in a pod")
|
|
containerConfigMap := make(map[string]*runtime.ContainerConfig)
|
|
for i := 0; i < 3; i++ {
|
|
cName := fmt.Sprintf("container%d", i)
|
|
containerConfig := ContainerConfig(
|
|
cName,
|
|
pauseImage,
|
|
WithTestLabels(),
|
|
WithTestAnnotations(),
|
|
)
|
|
cn, err := runtimeService.CreateContainer(sb, containerConfig, sbConfig)
|
|
containerConfigMap[cn] = containerConfig
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.RemoveContainer(cn))
|
|
}()
|
|
require.NoError(t, runtimeService.StartContainer(cn))
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.StopContainer(cn, 10))
|
|
}()
|
|
}
|
|
|
|
t.Logf("Fetch container stats for each container with Filter")
|
|
for id := range containerConfigMap {
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
stats, err = runtimeService.ListContainerStats(
|
|
&runtime.ContainerStatsFilter{Id: id})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if len(stats) != 1 {
|
|
return false, errors.New("unexpected stats length")
|
|
}
|
|
if stats[0].GetWritableLayer().GetTimestamp() != 0 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}, time.Second, 30*time.Second))
|
|
|
|
t.Logf("Verify container stats for %s", id)
|
|
for _, s := range stats {
|
|
require.Equal(t, s.GetAttributes().GetId(), id)
|
|
testStats(t, s, containerConfigMap[id])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test to verify filtering given a specific Sandbox ID. Stats for
|
|
// all the containers in a pod should be returned
|
|
func TestContainerListStatsWithSandboxIdFilter(t *testing.T) {
|
|
var (
|
|
stats []*runtime.ContainerStats
|
|
err error
|
|
)
|
|
t.Logf("Create a pod config and run sandbox container")
|
|
sb, sbConfig := PodSandboxConfigWithCleanup(t, "running-pod", "statsls")
|
|
|
|
pauseImage := images.Get(images.Pause)
|
|
EnsureImageExists(t, pauseImage)
|
|
|
|
t.Logf("Create a container config and run containers in a pod")
|
|
containerConfigMap := make(map[string]*runtime.ContainerConfig)
|
|
for i := 0; i < 3; i++ {
|
|
cName := fmt.Sprintf("container%d", i)
|
|
containerConfig := ContainerConfig(
|
|
cName,
|
|
pauseImage,
|
|
WithTestLabels(),
|
|
WithTestAnnotations(),
|
|
)
|
|
cn, err := runtimeService.CreateContainer(sb, containerConfig, sbConfig)
|
|
containerConfigMap[cn] = containerConfig
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.RemoveContainer(cn))
|
|
}()
|
|
require.NoError(t, runtimeService.StartContainer(cn))
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.StopContainer(cn, 10))
|
|
}()
|
|
}
|
|
|
|
t.Logf("Fetch container stats for each container with Filter")
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
stats, err = runtimeService.ListContainerStats(
|
|
&runtime.ContainerStatsFilter{PodSandboxId: sb})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if len(stats) != 3 {
|
|
return false, errors.New("unexpected stats length")
|
|
}
|
|
|
|
for _, containerStats := range stats {
|
|
// Wait for stats on all containers, not just the first one in the list.
|
|
if containerStats.GetWritableLayer().GetTimestamp() == 0 {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}, time.Second, 45*time.Second))
|
|
// TODO(claudiub): Reduce the timer above to 30 seconds once Windows flakiness has been addressed.
|
|
t.Logf("Verify container stats for sandbox %q", sb)
|
|
for _, s := range stats {
|
|
testStats(t, s, containerConfigMap[s.GetAttributes().GetId()])
|
|
}
|
|
}
|
|
|
|
// Test to verify filtering given a specific container ID and
|
|
// sandbox ID
|
|
func TestContainerListStatsWithIdSandboxIdFilter(t *testing.T) {
|
|
var (
|
|
stats []*runtime.ContainerStats
|
|
err error
|
|
)
|
|
t.Logf("Create a pod config and run sandbox container")
|
|
sb, sbConfig := PodSandboxConfigWithCleanup(t, "running-pod", "statsls")
|
|
|
|
pauseImage := images.Get(images.Pause)
|
|
EnsureImageExists(t, pauseImage)
|
|
|
|
t.Logf("Create container config and run containers in a pod")
|
|
containerConfigMap := make(map[string]*runtime.ContainerConfig)
|
|
for i := 0; i < 3; i++ {
|
|
cName := fmt.Sprintf("container%d", i)
|
|
containerConfig := ContainerConfig(
|
|
cName,
|
|
pauseImage,
|
|
WithTestLabels(),
|
|
WithTestAnnotations(),
|
|
)
|
|
cn, err := runtimeService.CreateContainer(sb, containerConfig, sbConfig)
|
|
containerConfigMap[cn] = containerConfig
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.RemoveContainer(cn))
|
|
}()
|
|
require.NoError(t, runtimeService.StartContainer(cn))
|
|
defer func() {
|
|
assert.NoError(t, runtimeService.StopContainer(cn, 10))
|
|
}()
|
|
}
|
|
t.Logf("Fetch container stats for sandbox ID and container ID filter")
|
|
for id, config := range containerConfigMap {
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
stats, err = runtimeService.ListContainerStats(
|
|
&runtime.ContainerStatsFilter{Id: id, PodSandboxId: sb})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if len(stats) != 1 {
|
|
return false, errors.New("unexpected stats length")
|
|
}
|
|
if stats[0].GetWritableLayer().GetTimestamp() != 0 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}, time.Second, 30*time.Second))
|
|
t.Logf("Verify container stats for sandbox %q and container %q filter", sb, id)
|
|
for _, s := range stats {
|
|
testStats(t, s, config)
|
|
}
|
|
}
|
|
|
|
t.Logf("Fetch container stats for sandbox truncID and container truncID filter ")
|
|
for id, config := range containerConfigMap {
|
|
require.NoError(t, Eventually(func() (bool, error) {
|
|
stats, err = runtimeService.ListContainerStats(
|
|
&runtime.ContainerStatsFilter{Id: id[:3], PodSandboxId: sb[:3]})
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if len(stats) != 1 {
|
|
return false, fmt.Errorf("expected only one stat, but got %v", stats)
|
|
}
|
|
if stats[0].GetWritableLayer().GetTimestamp() != 0 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}, time.Second, 30*time.Second))
|
|
t.Logf("Verify container stats for sandbox %q and container %q filter", sb, id)
|
|
for _, s := range stats {
|
|
testStats(t, s, config)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO make this as options to use for dead container tests
|
|
func testStats(t *testing.T,
|
|
s *runtime.ContainerStats,
|
|
config *runtime.ContainerConfig,
|
|
) {
|
|
require.NotEmpty(t, s.GetAttributes().GetId())
|
|
require.NotEmpty(t, s.GetAttributes().GetMetadata())
|
|
require.NotEmpty(t, s.GetAttributes().GetAnnotations())
|
|
require.Equal(t, s.GetAttributes().GetLabels(), config.Labels)
|
|
require.Equal(t, s.GetAttributes().GetAnnotations(), config.Annotations)
|
|
require.Equal(t, s.GetAttributes().GetMetadata().Name, config.Metadata.Name)
|
|
require.NotEmpty(t, s.GetAttributes().GetLabels())
|
|
require.NotEmpty(t, s.GetCpu().GetTimestamp())
|
|
require.NotEmpty(t, s.GetCpu().GetUsageCoreNanoSeconds().GetValue())
|
|
require.NotEmpty(t, s.GetMemory().GetTimestamp())
|
|
require.NotEmpty(t, s.GetMemory().GetWorkingSetBytes().GetValue())
|
|
require.NotEmpty(t, s.GetWritableLayer().GetTimestamp())
|
|
require.NotEmpty(t, s.GetWritableLayer().GetFsId().GetMountpoint())
|
|
|
|
// UsedBytes of a fresh container can be zero on Linux, depending on the backing filesystem.
|
|
// https://github.com/containerd/containerd/issues/7909
|
|
if goruntime.GOOS == "windows" {
|
|
require.NotEmpty(t, s.GetWritableLayer().GetUsedBytes().GetValue())
|
|
}
|
|
|
|
// Windows does not collect inodes stats.
|
|
if goruntime.GOOS != "windows" {
|
|
require.NotEmpty(t, s.GetWritableLayer().GetInodesUsed().GetValue())
|
|
}
|
|
}
|