diff --git a/integration/container_stats_test.go b/integration/container_stats_test.go new file mode 100644 index 000000000..11991aad9 --- /dev/null +++ b/integration/container_stats_test.go @@ -0,0 +1,322 @@ +/* +Copyright 2017 The Kubernetes 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 ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +// Test to verify for a container ID +func TestContainerStats(t *testing.T) { + t.Logf("Create a pod config and run sandbox container") + sbConfig := PodSandboxConfig("sandbox1", "stats") + sb, err := runtimeService.RunPodSandbox(sbConfig) + require.NoError(t, err) + defer func() { + assert.NoError(t, runtimeService.StopPodSandbox(sb)) + assert.NoError(t, runtimeService.RemovePodSandbox(sb)) + }() + 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().GetUsedBytes().GetValue() != 0 && + s.GetWritableLayer().GetInodesUsed().GetValue() != 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 filtering without any filter +func TestContainerListStats(t *testing.T) { + t.Logf("Create a pod config and run sandbox container") + sbConfig := PodSandboxConfig("running-pod", "statsls") + sb, err := runtimeService.RunPodSandbox(sbConfig) + require.NoError(t, err) + defer func() { + assert.NoError(t, runtimeService.StopPodSandbox(sb)) + assert.NoError(t, runtimeService.RemovePodSandbox(sb)) + }() + 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") + var stats []*runtime.ContainerStats + 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().GetUsedBytes().GetValue() == 0 && + s.GetWritableLayer().GetInodesUsed().GetValue() == 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) { + t.Logf("Create a pod config and run sandbox container") + sbConfig := PodSandboxConfig("running-pod", "statsls") + sb, err := runtimeService.RunPodSandbox(sbConfig) + require.NoError(t, err) + defer func() { + assert.NoError(t, runtimeService.StopPodSandbox(sb)) + assert.NoError(t, runtimeService.RemovePodSandbox(sb)) + }() + 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") + var stats []*runtime.ContainerStats + 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, fmt.Errorf("unexpected stats length") + } + if stats[0].GetWritableLayer().GetUsedBytes().GetValue() != 0 && + stats[0].GetWritableLayer().GetInodesUsed().GetValue() != 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) { + t.Logf("Create a pod config and run sandbox container") + sbConfig := PodSandboxConfig("running-pod", "statsls") + sb, err := runtimeService.RunPodSandbox(sbConfig) + require.NoError(t, err) + defer func() { + assert.NoError(t, runtimeService.StopPodSandbox(sb)) + assert.NoError(t, runtimeService.RemovePodSandbox(sb)) + }() + 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") + var stats []*runtime.ContainerStats + 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, fmt.Errorf("unexpected stats length") + } + if stats[0].GetWritableLayer().GetUsedBytes().GetValue() != 0 && + stats[0].GetWritableLayer().GetInodesUsed().GetValue() != 0 { + return true, nil + } + return false, nil + }, time.Second, 30*time.Second)) + 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) { + t.Logf("Create a pod config and run sandbox container") + sbConfig := PodSandboxConfig("running-pod", "statsls") + sb, err := runtimeService.RunPodSandbox(sbConfig) + require.NoError(t, err) + defer func() { + assert.NoError(t, runtimeService.StopPodSandbox(sb)) + assert.NoError(t, runtimeService.RemovePodSandbox(sb)) + }() + 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") + var stats []*runtime.ContainerStats + 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, fmt.Errorf("Unexpected stats length") + } + if stats[0].GetWritableLayer().GetUsedBytes().GetValue() != 0 && + stats[0].GetWritableLayer().GetInodesUsed().GetValue() != 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().GetStorageId().GetUuid()) + require.NotEmpty(t, s.GetWritableLayer().GetUsedBytes().GetValue()) + require.NotEmpty(t, s.GetWritableLayer().GetInodesUsed().GetValue()) + +} diff --git a/integration/test_utils.go b/integration/test_utils.go index 91cdc27fe..12783cb73 100644 --- a/integration/test_utils.go +++ b/integration/test_utils.go @@ -29,8 +29,9 @@ import ( ) const ( - sock = "/var/run/cri-containerd.sock" - timeout = 1 * time.Minute + sock = "/var/run/cri-containerd.sock" + timeout = 1 * time.Minute + pauseImage = "gcr.io/google_containers/pause:3.0" ) var ( @@ -71,6 +72,37 @@ func PodSandboxConfig(name, ns string, opts ...PodSandboxOpts) *runtime.PodSandb return config } +// ContainerOpts to set any specific attribute like labels, +// annotations, metadata etc +type ContainerOpts func(*runtime.ContainerConfig) + +func WithTestLabels() ContainerOpts { + return func(cf *runtime.ContainerConfig) { + cf.Labels = map[string]string{"key": "value"} + } +} + +func WithTestAnnotations() ContainerOpts { + return func(cf *runtime.ContainerConfig) { + cf.Annotations = map[string]string{"a.b.c": "test"} + } +} + +// ContainerConfig creates a container config given a name and image name +// and additional container config options +func ContainerConfig(name, image string, opts ...ContainerOpts) *runtime.ContainerConfig { + cConfig := &runtime.ContainerConfig{ + Metadata: &runtime.ContainerMetadata{ + Name: name, + }, + Image: &runtime.ImageSpec{Image: image}, + } + for _, opt := range opts { + opt(cConfig) + } + return cConfig +} + // CheckFunc is the function used to check a condition is true/false. type CheckFunc func() (bool, error)