talos/internal/pkg/containers/containerd/containerd_test.go
Andrey Smirnov 7f0300f108
feat: update dependencies, Kubernetes 1.34.0-alpha.2
Bump all dependencies, many small changes due to new golangci-lint
version.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2025-06-30 19:05:22 +04:00

385 lines
12 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package containerd_test
import (
"context"
"encoding/hex"
"os"
"path/filepath"
"sync"
"testing"
"github.com/containerd/cgroups/v3"
"github.com/containerd/cgroups/v3/cgroup1"
"github.com/containerd/cgroups/v3/cgroup2"
containerd "github.com/containerd/containerd/v2/client"
"github.com/containerd/containerd/v2/pkg/namespaces"
"github.com/containerd/containerd/v2/pkg/oci"
"github.com/google/uuid"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/suite"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/logging"
"github.com/siderolabs/talos/internal/app/machined/pkg/system/events"
"github.com/siderolabs/talos/internal/app/machined/pkg/system/runner"
containerdrunner "github.com/siderolabs/talos/internal/app/machined/pkg/system/runner/containerd"
"github.com/siderolabs/talos/internal/app/machined/pkg/system/runner/process"
ctrd "github.com/siderolabs/talos/internal/pkg/containers/containerd"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
const (
busyboxImage = "docker.io/library/busybox:1.30.1"
busyboxImageDigest = "sha256:4b6ad3a68d34da29bf7c8ccb5d355ba8b4babcad1f99798204e7abb43e54ee3d"
)
func MockEventSink(state events.ServiceState, message string, args ...any) {
}
//nolint:maligned
type ContainerdSuite struct {
suite.Suite
tmpDir string
loggingManager runtime.LoggingManager
containerdNamespace string
containerdRunner runner.Runner
containerdWg sync.WaitGroup
containerdAddress string
containerID string
client *containerd.Client
image containerd.Image
containerRunners []runner.Runner
containersWg sync.WaitGroup
}
func (suite *ContainerdSuite) SetupSuite() {
if cgroups.Mode() == cgroups.Unified {
suite.T().Skip("test doesn't pass under cgroupsv2")
}
var err error
suite.tmpDir = suite.T().TempDir()
suite.loggingManager = logging.NewFileLoggingManager(suite.tmpDir)
stateDir, rootDir := filepath.Join(suite.tmpDir, "state"), filepath.Join(suite.tmpDir, "root")
suite.Require().NoError(os.Mkdir(stateDir, 0o777))
suite.Require().NoError(os.Mkdir(rootDir, 0o777))
suite.containerdAddress = filepath.Join(suite.tmpDir, "run.sock")
if cgroups.Mode() == cgroups.Unified {
var (
groupPath string
manager *cgroup2.Manager
)
groupPath, err = cgroup2.NestedGroupPath(suite.tmpDir)
suite.Require().NoError(err)
manager, err = cgroup2.NewManager(constants.CgroupMountPath, groupPath, &cgroup2.Resources{})
suite.Require().NoError(err)
defer manager.Delete() //nolint:errcheck
} else {
var manager cgroup1.Cgroup
manager, err = cgroup1.New(cgroup1.NestedPath(suite.tmpDir), &specs.LinuxResources{})
suite.Require().NoError(err)
defer manager.Delete() //nolint:errcheck
}
args := &runner.Args{
ID: "containerd",
ProcessArgs: []string{
"/bin/containerd",
"--address", suite.containerdAddress,
"--state", stateDir,
"--root", rootDir,
"--config", constants.CRIContainerdConfig,
},
}
suite.containerdRunner = process.NewRunner(
false,
args,
runner.WithLoggingManager(suite.loggingManager),
runner.WithEnv([]string{"PATH=/bin:" + constants.PATH}),
runner.WithCgroupPath(suite.tmpDir),
)
suite.Require().NoError(suite.containerdRunner.Open())
suite.containerdWg.Add(1)
go func() {
defer suite.containerdWg.Done()
defer suite.containerdRunner.Close() //nolint:errcheck
suite.containerdRunner.Run(MockEventSink) //nolint:errcheck
}()
suite.client, err = containerd.New(suite.containerdAddress)
suite.Require().NoError(err)
namespace := ([16]byte)(uuid.New())
suite.containerdNamespace = "talos" + hex.EncodeToString(namespace[:])
ctx := namespaces.WithNamespace(context.Background(), suite.containerdNamespace)
suite.image, err = suite.client.Pull(ctx, busyboxImage, containerd.WithPullUnpack)
suite.Require().NoError(err)
}
func (suite *ContainerdSuite) TearDownSuite() {
suite.Require().NoError(suite.client.Close())
suite.Require().NoError(suite.containerdRunner.Stop())
suite.containerdWg.Wait()
}
func (suite *ContainerdSuite) SetupTest() {
suite.containerRunners = nil
suite.containerID = uuid.New().String()
}
func (suite *ContainerdSuite) run(runners ...runner.Runner) {
runningCh := make(chan bool, 2*len(runners))
for _, r := range runners {
suite.Require().NoError(r.Open())
suite.containerRunners = append(suite.containerRunners, r)
suite.containersWg.Add(1)
go func(r runner.Runner) {
runningSink := func(state events.ServiceState, message string, args ...any) {
if state == events.StateRunning {
runningCh <- true
}
}
defer func() { runningCh <- false }()
defer suite.containersWg.Done()
suite.Require().NoError(r.Run(runningSink))
}(r)
}
// wait for the containers to be started actually
for range runners {
result := <-runningCh
suite.Require().True(result, "some containers failed to start")
}
}
func (suite *ContainerdSuite) TearDownTest() {
for _, r := range suite.containerRunners {
suite.Assert().NoError(r.Stop())
}
suite.containersWg.Wait()
for _, r := range suite.containerRunners {
suite.Assert().NoError(r.Close())
}
}
func (suite *ContainerdSuite) runK8sContainers() {
suite.run(containerdrunner.NewRunner(false, &runner.Args{
ID: suite.containerID + "1",
ProcessArgs: []string{"/bin/sh", "-c", "sleep 3600"},
},
runner.WithLoggingManager(suite.loggingManager),
runner.WithNamespace(suite.containerdNamespace),
runner.WithContainerImage(busyboxImage),
runner.WithContainerOpts(containerd.WithContainerLabels(map[string]string{
"io.kubernetes.pod.name": "fun",
"io.kubernetes.pod.namespace": "ns1",
})),
runner.WithOCISpecOpts(oci.WithAnnotations(map[string]string{
"io.kubernetes.cri.sandbox-log-directory": "sandbox",
"io.kubernetes.cri.sandbox-id": "c888d69b73b5b444c2b0bd70da28c3da102b0aeb327f3a297626e2558def327f",
})),
runner.WithContainerdAddress(suite.containerdAddress),
), containerdrunner.NewRunner(false, &runner.Args{
ID: suite.containerID + "2",
ProcessArgs: []string{"/bin/sh", "-c", "sleep 3600"},
},
runner.WithLoggingManager(suite.loggingManager),
runner.WithNamespace(suite.containerdNamespace),
runner.WithContainerImage(busyboxImage),
runner.WithContainerOpts(containerd.WithContainerLabels(map[string]string{
"io.kubernetes.pod.name": "fun",
"io.kubernetes.pod.namespace": "ns1",
"io.kubernetes.container.name": "run",
})),
runner.WithOCISpecOpts(oci.WithAnnotations(map[string]string{
"io.kubernetes.cri.sandbox-id": "c888d69b73b5b444c2b0bd70da28c3da102b0aeb327f3a297626e2558def327f",
})),
runner.WithContainerdAddress(suite.containerdAddress),
))
}
func (suite *ContainerdSuite) TestPodsNonK8s() {
suite.run(containerdrunner.NewRunner(false, &runner.Args{
ID: suite.containerID,
ProcessArgs: []string{"/bin/sh", "-c", "sleep 3600"},
},
runner.WithLoggingManager(suite.loggingManager),
runner.WithNamespace(suite.containerdNamespace),
runner.WithContainerImage(busyboxImage),
runner.WithContainerdAddress(suite.containerdAddress),
))
i, err := ctrd.NewInspector(context.Background(), suite.containerdNamespace, ctrd.WithContainerdAddress(suite.containerdAddress))
suite.Assert().NoError(err)
pods, err := i.Pods()
suite.Require().NoError(err)
suite.Require().Len(pods, 1)
suite.Assert().Equal(suite.containerID, pods[0].Name)
suite.Assert().Equal("", pods[0].Sandbox)
suite.Require().Len(pods[0].Containers, 1)
suite.Assert().Equal(suite.containerID, pods[0].Containers[0].Display)
suite.Assert().Equal(suite.containerID, pods[0].Containers[0].Name)
suite.Assert().Equal(suite.containerID, pods[0].Containers[0].ID)
suite.Assert().Equal(busyboxImage, pods[0].Containers[0].Image)
suite.Assert().Equal("RUNNING", pods[0].Containers[0].Status)
suite.Assert().NotNil(pods[0].Containers[0].Metrics)
suite.Assert().NoError(i.Close())
}
func (suite *ContainerdSuite) TestPodsK8s() {
suite.runK8sContainers()
i, err := ctrd.NewInspector(context.Background(), suite.containerdNamespace, ctrd.WithContainerdAddress(suite.containerdAddress))
suite.Assert().NoError(err)
pods, err := i.Pods()
suite.Require().NoError(err)
suite.Require().Len(pods, 1)
suite.Assert().Equal("ns1/fun", pods[0].Name)
suite.Assert().Equal("sandbox", pods[0].Sandbox)
suite.Require().Len(pods[0].Containers, 2)
suite.Assert().Equal("ns1/fun", pods[0].Containers[0].Display)
suite.Assert().Equal(suite.containerID+"1", pods[0].Containers[0].Name)
suite.Assert().Equal(suite.containerID+"1", pods[0].Containers[0].ID)
suite.Assert().Equal("sandbox", pods[0].Containers[0].Sandbox)
suite.Assert().Equal("0", pods[0].Containers[0].RestartCount)
suite.Assert().Equal("", pods[0].Containers[0].GetLogFile())
suite.Assert().Equal(busyboxImage, pods[0].Containers[0].Image)
suite.Assert().Equal("RUNNING", pods[0].Containers[0].Status)
suite.Assert().NotNil(pods[0].Containers[0].Metrics)
suite.Assert().Equal("ns1/fun:run", pods[0].Containers[1].Display)
suite.Assert().Equal("run", pods[0].Containers[1].Name)
suite.Assert().Equal(suite.containerID+"2", pods[0].Containers[1].ID)
suite.Assert().Equal("sandbox", pods[0].Containers[1].Sandbox)
suite.Assert().Equal("sandbox/run/0.log", pods[0].Containers[1].GetLogFile())
suite.Assert().Equal(busyboxImage, pods[0].Containers[1].Image)
suite.Assert().Equal("RUNNING", pods[0].Containers[1].Status)
suite.Assert().NotNil(pods[0].Containers[1].Metrics)
suite.Assert().NoError(i.Close())
}
func (suite *ContainerdSuite) TestContainerNonK8s() {
suite.run(containerdrunner.NewRunner(false, &runner.Args{
ID: suite.containerID,
ProcessArgs: []string{"/bin/sh", "-c", "sleep 3600"},
},
runner.WithLoggingManager(suite.loggingManager),
runner.WithNamespace(suite.containerdNamespace),
runner.WithContainerImage(busyboxImage),
runner.WithContainerdAddress(suite.containerdAddress),
))
i, err := ctrd.NewInspector(context.Background(), suite.containerdNamespace, ctrd.WithContainerdAddress(suite.containerdAddress))
suite.Assert().NoError(err)
cntr, err := i.Container(suite.containerID)
suite.Require().NoError(err)
suite.Require().NotNil(cntr)
suite.Assert().Equal(suite.containerID, cntr.Name)
suite.Assert().Equal(suite.containerID, cntr.Display)
suite.Assert().Equal(suite.containerID, cntr.ID)
suite.Assert().Equal(busyboxImageDigest, cntr.Image) // image is not resolved
suite.Assert().Equal("RUNNING", cntr.Status)
cntr, err = i.Container("nosuchcontainer")
suite.Require().NoError(err)
suite.Require().Nil(cntr)
suite.Assert().NoError(i.Close())
}
func (suite *ContainerdSuite) TestContainerK8s() {
suite.runK8sContainers()
i, err := ctrd.NewInspector(context.Background(), suite.containerdNamespace, ctrd.WithContainerdAddress(suite.containerdAddress))
suite.Assert().NoError(err)
cntr, err := i.Container("ns1/fun")
suite.Require().NoError(err)
suite.Require().NotNil(cntr)
suite.Assert().Equal(suite.containerID+"1", cntr.Name)
suite.Assert().Equal("ns1/fun", cntr.Display)
suite.Assert().Equal(suite.containerID+"1", cntr.ID)
suite.Assert().Equal("sandbox", cntr.Sandbox)
suite.Assert().Equal("", cntr.GetLogFile())
suite.Assert().Equal(busyboxImageDigest, cntr.Image) // image is not resolved
suite.Assert().Equal("RUNNING", cntr.Status)
cntr, err = i.Container("ns1/fun:run")
suite.Require().NoError(err)
suite.Require().NotNil(cntr)
suite.Assert().Equal("run", cntr.Name)
suite.Assert().Equal("ns1/fun:run", cntr.Display)
suite.Assert().Equal(suite.containerID+"2", cntr.ID)
suite.Assert().Equal("sandbox", cntr.Sandbox)
suite.Assert().Equal("sandbox/run/0.log", cntr.GetLogFile())
suite.Assert().Equal(busyboxImageDigest, cntr.Image) // image is not resolved
suite.Assert().Equal("RUNNING", cntr.Status)
cntr, err = i.Container("ns2/fun:run")
suite.Require().NoError(err)
suite.Require().Nil(cntr)
cntr, err = i.Container("ns1/run:run")
suite.Require().NoError(err)
suite.Require().Nil(cntr)
cntr, err = i.Container("ns1/fun:go")
suite.Require().NoError(err)
suite.Require().Nil(cntr)
suite.Assert().NoError(i.Close())
}
func TestContainerdSuite(t *testing.T) {
if os.Getuid() != 0 {
t.Skip("can't run the test as non-root")
}
_, err := os.Stat("/bin/containerd")
if err != nil {
t.Skip("containerd binary is not available, skipping the test")
}
suite.Run(t, new(ContainerdSuite))
}