Add integration test.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
405f57f8e0
commit
bf551b9c4c
113
integration/container_log_test.go
Normal file
113
integration/container_log_test.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 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 (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLongContainerLog(t *testing.T) {
|
||||||
|
testPodLogDir, err := ioutil.TempDir("/tmp", "long-container-log")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(testPodLogDir)
|
||||||
|
|
||||||
|
t.Log("Create a sandbox with log directory")
|
||||||
|
sbConfig := PodSandboxConfig("sandbox", "long-container-log",
|
||||||
|
WithPodLogDirectory(testPodLogDir),
|
||||||
|
)
|
||||||
|
sb, err := runtimeService.RunPodSandbox(sbConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.NoError(t, runtimeService.StopPodSandbox(sb))
|
||||||
|
assert.NoError(t, runtimeService.RemovePodSandbox(sb))
|
||||||
|
}()
|
||||||
|
|
||||||
|
const (
|
||||||
|
testImage = "busybox"
|
||||||
|
containerName = "test-container"
|
||||||
|
)
|
||||||
|
t.Logf("Pull test image %q", testImage)
|
||||||
|
img, err := imageService.PullImage(&runtime.ImageSpec{Image: testImage}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.NoError(t, imageService.RemoveImage(&runtime.ImageSpec{Image: img}))
|
||||||
|
}()
|
||||||
|
|
||||||
|
t.Log("Create a container with log path")
|
||||||
|
config, err := CRIConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
maxSize := config.MaxContainerLogLineSize
|
||||||
|
shortLineCmd := fmt.Sprintf("i=0; while [ $i -lt %d ]; do printf %s; i=$((i+1)); done", maxSize-1, "a")
|
||||||
|
maxLenLineCmd := fmt.Sprintf("i=0; while [ $i -lt %d ]; do printf %s; i=$((i+1)); done", maxSize, "b")
|
||||||
|
longLineCmd := fmt.Sprintf("i=0; while [ $i -lt %d ]; do printf %s; i=$((i+1)); done", maxSize+1, "c")
|
||||||
|
cnConfig := ContainerConfig(
|
||||||
|
containerName,
|
||||||
|
"busybox",
|
||||||
|
WithCommand("sh", "-c",
|
||||||
|
fmt.Sprintf("%s; echo; %s; echo; %s", shortLineCmd, maxLenLineCmd, longLineCmd)),
|
||||||
|
WithLogPath(containerName),
|
||||||
|
)
|
||||||
|
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Log("Start the container")
|
||||||
|
require.NoError(t, runtimeService.StartContainer(cn))
|
||||||
|
|
||||||
|
t.Log("Wait for container to finish running")
|
||||||
|
require.NoError(t, Eventually(func() (bool, error) {
|
||||||
|
s, err := runtimeService.ContainerStatus(cn)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if s.GetState() == runtime.ContainerState_CONTAINER_EXITED {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}, time.Second, 30*time.Second))
|
||||||
|
|
||||||
|
t.Log("Check container log")
|
||||||
|
content, err := ioutil.ReadFile(filepath.Join(testPodLogDir, containerName))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkContainerLog(t, string(content), []string{
|
||||||
|
fmt.Sprintf("%s %s %s", runtime.Stdout, runtime.LogTagFull, strings.Repeat("a", maxSize-1)),
|
||||||
|
fmt.Sprintf("%s %s %s", runtime.Stdout, runtime.LogTagFull, strings.Repeat("b", maxSize)),
|
||||||
|
fmt.Sprintf("%s %s %s", runtime.Stdout, runtime.LogTagPartial, strings.Repeat("c", maxSize)),
|
||||||
|
fmt.Sprintf("%s %s %s", runtime.Stdout, runtime.LogTagFull, "c"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkContainerLog(t *testing.T, log string, messages []string) {
|
||||||
|
lines := strings.Split(strings.TrimSpace(log), "\n")
|
||||||
|
require.Len(t, lines, len(messages), "log line number should match")
|
||||||
|
for i, line := range lines {
|
||||||
|
parts := strings.SplitN(line, " ", 2)
|
||||||
|
require.Len(t, parts, 2)
|
||||||
|
_, err := time.Parse(time.RFC3339Nano, parts[0])
|
||||||
|
assert.NoError(t, err, "timestamp should be in RFC3339Nano format")
|
||||||
|
assert.Equal(t, messages[i], parts[1], "log content should match")
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,7 @@ func checkMemoryLimit(t *testing.T, spec *runtimespec.Spec, memLimit int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateContainerResources(t *testing.T) {
|
func TestUpdateContainerResources(t *testing.T) {
|
||||||
t.Logf("Create a sandbox")
|
t.Log("Create a sandbox")
|
||||||
sbConfig := PodSandboxConfig("sandbox", "update-container-resources")
|
sbConfig := PodSandboxConfig("sandbox", "update-container-resources")
|
||||||
sb, err := runtimeService.RunPodSandbox(sbConfig)
|
sb, err := runtimeService.RunPodSandbox(sbConfig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -46,7 +46,7 @@ func TestUpdateContainerResources(t *testing.T) {
|
|||||||
assert.NoError(t, runtimeService.RemovePodSandbox(sb))
|
assert.NoError(t, runtimeService.RemovePodSandbox(sb))
|
||||||
}()
|
}()
|
||||||
|
|
||||||
t.Logf("Create a container with memory limit")
|
t.Log("Create a container with memory limit")
|
||||||
cnConfig := ContainerConfig(
|
cnConfig := ContainerConfig(
|
||||||
"container",
|
"container",
|
||||||
pauseImage,
|
pauseImage,
|
||||||
@ -57,48 +57,48 @@ func TestUpdateContainerResources(t *testing.T) {
|
|||||||
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
|
cn, err := runtimeService.CreateContainer(sb, cnConfig, sbConfig)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Logf("Check memory limit in container OCI spec")
|
t.Log("Check memory limit in container OCI spec")
|
||||||
container, err := containerdClient.LoadContainer(context.Background(), cn)
|
container, err := containerdClient.LoadContainer(context.Background(), cn)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
spec, err := container.Spec(context.Background())
|
spec, err := container.Spec(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkMemoryLimit(t, spec, 2*1024*1024)
|
checkMemoryLimit(t, spec, 2*1024*1024)
|
||||||
|
|
||||||
t.Logf("Update container memory limit after created")
|
t.Log("Update container memory limit after created")
|
||||||
err = runtimeService.UpdateContainerResources(cn, &runtime.LinuxContainerResources{
|
err = runtimeService.UpdateContainerResources(cn, &runtime.LinuxContainerResources{
|
||||||
MemoryLimitInBytes: 4 * 1024 * 1024,
|
MemoryLimitInBytes: 4 * 1024 * 1024,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Logf("Check memory limit in container OCI spec")
|
t.Log("Check memory limit in container OCI spec")
|
||||||
spec, err = container.Spec(context.Background())
|
spec, err = container.Spec(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkMemoryLimit(t, spec, 4*1024*1024)
|
checkMemoryLimit(t, spec, 4*1024*1024)
|
||||||
|
|
||||||
t.Logf("Start the container")
|
t.Log("Start the container")
|
||||||
require.NoError(t, runtimeService.StartContainer(cn))
|
require.NoError(t, runtimeService.StartContainer(cn))
|
||||||
task, err := container.Task(context.Background(), nil)
|
task, err := container.Task(context.Background(), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Logf("Check memory limit in cgroup")
|
t.Log("Check memory limit in cgroup")
|
||||||
cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid())))
|
cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid())))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
stat, err := cgroup.Stat(cgroups.IgnoreNotExist)
|
stat, err := cgroup.Stat(cgroups.IgnoreNotExist)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint64(4*1024*1024), stat.Memory.Usage.Limit)
|
assert.Equal(t, uint64(4*1024*1024), stat.Memory.Usage.Limit)
|
||||||
|
|
||||||
t.Logf("Update container memory limit after started")
|
t.Log("Update container memory limit after started")
|
||||||
err = runtimeService.UpdateContainerResources(cn, &runtime.LinuxContainerResources{
|
err = runtimeService.UpdateContainerResources(cn, &runtime.LinuxContainerResources{
|
||||||
MemoryLimitInBytes: 8 * 1024 * 1024,
|
MemoryLimitInBytes: 8 * 1024 * 1024,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Logf("Check memory limit in container OCI spec")
|
t.Log("Check memory limit in container OCI spec")
|
||||||
spec, err = container.Spec(context.Background())
|
spec, err = container.Spec(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
checkMemoryLimit(t, spec, 8*1024*1024)
|
checkMemoryLimit(t, spec, 8*1024*1024)
|
||||||
|
|
||||||
t.Logf("Check memory limit in cgroup")
|
t.Log("Check memory limit in cgroup")
|
||||||
stat, err = cgroup.Stat(cgroups.IgnoreNotExist)
|
stat, err = cgroup.Stat(cgroups.IgnoreNotExist)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint64(8*1024*1024), stat.Memory.Usage.Limit)
|
assert.Equal(t, uint64(8*1024*1024), stat.Memory.Usage.Limit)
|
||||||
|
@ -18,6 +18,7 @@ package integration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -28,12 +29,15 @@ import (
|
|||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"google.golang.org/grpc"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/cri"
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri"
|
||||||
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/remote"
|
"k8s.io/kubernetes/pkg/kubelet/remote"
|
||||||
|
kubeletutil "k8s.io/kubernetes/pkg/kubelet/util"
|
||||||
|
|
||||||
api "github.com/containerd/cri/pkg/api/v1"
|
api "github.com/containerd/cri/pkg/api/v1"
|
||||||
"github.com/containerd/cri/pkg/client"
|
"github.com/containerd/cri/pkg/client"
|
||||||
|
criconfig "github.com/containerd/cri/pkg/config"
|
||||||
"github.com/containerd/cri/pkg/constants"
|
"github.com/containerd/cri/pkg/constants"
|
||||||
"github.com/containerd/cri/pkg/util"
|
"github.com/containerd/cri/pkg/util"
|
||||||
)
|
)
|
||||||
@ -100,6 +104,7 @@ func ConnectDaemons() error {
|
|||||||
// Opts sets specific information in pod sandbox config.
|
// Opts sets specific information in pod sandbox config.
|
||||||
type PodSandboxOpts func(*runtime.PodSandboxConfig)
|
type PodSandboxOpts func(*runtime.PodSandboxConfig)
|
||||||
|
|
||||||
|
// Set host network.
|
||||||
func WithHostNetwork(p *runtime.PodSandboxConfig) {
|
func WithHostNetwork(p *runtime.PodSandboxConfig) {
|
||||||
if p.Linux == nil {
|
if p.Linux == nil {
|
||||||
p.Linux = &runtime.LinuxPodSandboxConfig{}
|
p.Linux = &runtime.LinuxPodSandboxConfig{}
|
||||||
@ -114,6 +119,13 @@ func WithHostNetwork(p *runtime.PodSandboxConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add pod log directory.
|
||||||
|
func WithPodLogDirectory(dir string) PodSandboxOpts {
|
||||||
|
return func(p *runtime.PodSandboxConfig) {
|
||||||
|
p.LogDirectory = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PodSandboxConfig generates a pod sandbox config for test.
|
// PodSandboxConfig generates a pod sandbox config for test.
|
||||||
func PodSandboxConfig(name, ns string, opts ...PodSandboxOpts) *runtime.PodSandboxConfig {
|
func PodSandboxConfig(name, ns string, opts ...PodSandboxOpts) *runtime.PodSandboxConfig {
|
||||||
config := &runtime.PodSandboxConfig{
|
config := &runtime.PodSandboxConfig{
|
||||||
@ -137,52 +149,59 @@ func PodSandboxConfig(name, ns string, opts ...PodSandboxOpts) *runtime.PodSandb
|
|||||||
type ContainerOpts func(*runtime.ContainerConfig)
|
type ContainerOpts func(*runtime.ContainerConfig)
|
||||||
|
|
||||||
func WithTestLabels() ContainerOpts {
|
func WithTestLabels() ContainerOpts {
|
||||||
return func(cf *runtime.ContainerConfig) {
|
return func(c *runtime.ContainerConfig) {
|
||||||
cf.Labels = map[string]string{"key": "value"}
|
c.Labels = map[string]string{"key": "value"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithTestAnnotations() ContainerOpts {
|
func WithTestAnnotations() ContainerOpts {
|
||||||
return func(cf *runtime.ContainerConfig) {
|
return func(c *runtime.ContainerConfig) {
|
||||||
cf.Annotations = map[string]string{"a.b.c": "test"}
|
c.Annotations = map[string]string{"a.b.c": "test"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add container resource limits.
|
// Add container resource limits.
|
||||||
func WithResources(r *runtime.LinuxContainerResources) ContainerOpts {
|
func WithResources(r *runtime.LinuxContainerResources) ContainerOpts {
|
||||||
return func(cf *runtime.ContainerConfig) {
|
return func(c *runtime.ContainerConfig) {
|
||||||
if cf.Linux == nil {
|
if c.Linux == nil {
|
||||||
cf.Linux = &runtime.LinuxContainerConfig{}
|
c.Linux = &runtime.LinuxContainerConfig{}
|
||||||
}
|
}
|
||||||
cf.Linux.Resources = r
|
c.Linux.Resources = r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add container command.
|
// Add container command.
|
||||||
func WithCommand(c string, args ...string) ContainerOpts {
|
func WithCommand(cmd string, args ...string) ContainerOpts {
|
||||||
return func(cf *runtime.ContainerConfig) {
|
return func(c *runtime.ContainerConfig) {
|
||||||
cf.Command = []string{c}
|
c.Command = []string{cmd}
|
||||||
cf.Args = args
|
c.Args = args
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add pid namespace mode.
|
// Add pid namespace mode.
|
||||||
func WithPidNamespace(mode runtime.NamespaceMode) ContainerOpts {
|
func WithPidNamespace(mode runtime.NamespaceMode) ContainerOpts {
|
||||||
return func(cf *runtime.ContainerConfig) {
|
return func(c *runtime.ContainerConfig) {
|
||||||
if cf.Linux == nil {
|
if c.Linux == nil {
|
||||||
cf.Linux = &runtime.LinuxContainerConfig{}
|
c.Linux = &runtime.LinuxContainerConfig{}
|
||||||
}
|
}
|
||||||
if cf.Linux.SecurityContext == nil {
|
if c.Linux.SecurityContext == nil {
|
||||||
cf.Linux.SecurityContext = &runtime.LinuxContainerSecurityContext{}
|
c.Linux.SecurityContext = &runtime.LinuxContainerSecurityContext{}
|
||||||
}
|
}
|
||||||
if cf.Linux.SecurityContext.NamespaceOptions == nil {
|
if c.Linux.SecurityContext.NamespaceOptions == nil {
|
||||||
cf.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
|
c.Linux.SecurityContext.NamespaceOptions = &runtime.NamespaceOption{}
|
||||||
}
|
}
|
||||||
cf.Linux.SecurityContext.NamespaceOptions.Pid = mode
|
c.Linux.SecurityContext.NamespaceOptions.Pid = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add container log path.
|
||||||
|
func WithLogPath(path string) ContainerOpts {
|
||||||
|
return func(c *runtime.ContainerConfig) {
|
||||||
|
c.LogPath = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ContainerConfig creates a container config given a name and image name
|
// ContainerConfig creates a container config given a name and image name
|
||||||
// and additional container config options
|
// and additional container config options
|
||||||
func ContainerConfig(name, image string, opts ...ContainerOpts) *runtime.ContainerConfig {
|
func ContainerConfig(name, image string, opts ...ContainerOpts) *runtime.ContainerConfig {
|
||||||
@ -247,3 +266,27 @@ func PidOf(name string) (int, error) {
|
|||||||
}
|
}
|
||||||
return strconv.Atoi(output)
|
return strconv.Atoi(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRIConfig gets current cri config from containerd.
|
||||||
|
func CRIConfig() (*criconfig.Config, error) {
|
||||||
|
addr, dialer, err := kubeletutil.GetAddressAndDialer(*criEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get dialer")
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithDialer(dialer))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to connect cri endpoint")
|
||||||
|
}
|
||||||
|
client := runtime.NewRuntimeServiceClient(conn)
|
||||||
|
resp, err := client.Status(ctx, &runtime.StatusRequest{Verbose: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get status")
|
||||||
|
}
|
||||||
|
config := &criconfig.Config{}
|
||||||
|
if err := json.Unmarshal([]byte(resp.Info["config"]), config); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal config")
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user