diff --git a/integration/client/container_linux_test.go b/integration/client/container_linux_test.go index 5da681309..4de3f4477 100644 --- a/integration/client/container_linux_test.go +++ b/integration/client/container_linux_test.go @@ -238,76 +238,6 @@ func TestShimInCgroup(t *testing.T) { <-statusC } -func TestDaemonRestart(t *testing.T) { - client, err := newClient(t, address) - if err != nil { - t.Fatal(err) - } - defer client.Close() - - var ( - image Image - ctx, cancel = testContext(t) - id = t.Name() - ) - defer cancel() - - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } - - container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "30"))) - if err != nil { - t.Fatal(err) - } - defer container.Delete(ctx, WithSnapshotCleanup) - - task, err := container.NewTask(ctx, empty()) - if err != nil { - t.Fatal(err) - } - defer task.Delete(ctx) - - statusC, err := task.Wait(ctx) - if err != nil { - t.Fatal(err) - } - - if err := task.Start(ctx); err != nil { - t.Fatal(err) - } - - var exitStatus ExitStatus - if err := ctrd.Restart(func() { - exitStatus = <-statusC - }); err != nil { - t.Fatal(err) - } - - if exitStatus.Error() == nil { - t.Errorf(`first task.Wait() should have failed with "transport is closing"`) - } - - waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second) - serving, err := client.IsServing(waitCtx) - waitCancel() - if !serving { - t.Fatalf("containerd did not start within 2s: %v", err) - } - - statusC, err = task.Wait(ctx) - if err != nil { - t.Fatal(err) - } - - if err := task.Kill(ctx, syscall.SIGKILL); err != nil { - t.Fatal(err) - } - - <-statusC -} - func TestShimDoesNotLeakPipes(t *testing.T) { containerdPid := ctrd.cmd.Process.Pid initialPipes, err := numPipes(containerdPid) @@ -690,57 +620,6 @@ func TestContainerAttach(t *testing.T) { } } -func newDirectIO(ctx context.Context, terminal bool) (*directIO, error) { - fifos, err := cio.NewFIFOSetInDir("", "", terminal) - if err != nil { - return nil, err - } - dio, err := cio.NewDirectIO(ctx, fifos) - if err != nil { - return nil, err - } - return &directIO{DirectIO: *dio}, nil -} - -type directIO struct { - cio.DirectIO -} - -// ioCreate returns IO available for use with task creation -func (f *directIO) IOCreate(id string) (cio.IO, error) { - return f, nil -} - -// ioAttach returns IO available for use with task attachment -func (f *directIO) IOAttach(set *cio.FIFOSet) (cio.IO, error) { - return f, nil -} - -func (f *directIO) Cancel() { - // nothing to cancel as all operations are handled externally -} - -// Close closes all open fds -func (f *directIO) Close() error { - err := f.Stdin.Close() - if f.Stdout != nil { - if err2 := f.Stdout.Close(); err == nil { - err = err2 - } - } - if f.Stderr != nil { - if err2 := f.Stderr.Close(); err == nil { - err = err2 - } - } - return err -} - -// Delete removes the underlying directory containing fifos -func (f *directIO) Delete() error { - return f.DirectIO.Close() -} - func TestContainerUsername(t *testing.T) { t.Parallel() @@ -1372,85 +1251,10 @@ func TestContainerRuntimeOptionsv2(t *testing.T) { } } -func initContainerAndCheckChildrenDieOnKill(t *testing.T, opts ...oci.SpecOpts) { - client, err := newClient(t, address) - if err != nil { - t.Fatal(err) - } - defer client.Close() - - var ( - image Image - ctx, cancel = testContext(t) - id = t.Name() - ) - defer cancel() - - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } - - opts = append(opts, oci.WithImageConfig(image)) - opts = append(opts, withProcessArgs("sh", "-c", "sleep 42; echo hi")) - - container, err := client.NewContainer(ctx, id, - WithNewSnapshot(id, image), - WithNewSpec(opts...), - ) - if err != nil { - t.Fatal(err) - } - defer container.Delete(ctx, WithSnapshotCleanup) - - stdout := bytes.NewBuffer(nil) - task, err := container.NewTask(ctx, cio.NewCreator(withByteBuffers(stdout))) - if err != nil { - t.Fatal(err) - } - defer task.Delete(ctx) - - statusC, err := task.Wait(ctx) - if err != nil { - t.Fatal(err) - } - - if err := task.Start(ctx); err != nil { - t.Fatal(err) - } - - if err := task.Kill(ctx, syscall.SIGKILL); err != nil { - t.Error(err) - } - - // Give the shim time to reap the init process and kill the orphans - select { - case <-statusC: - case <-time.After(100 * time.Millisecond): - } - - b, err := exec.Command("ps", "ax").CombinedOutput() - if err != nil { - t.Fatal(err) - } - - if strings.Contains(string(b), "sleep 42") { - t.Fatalf("killing init didn't kill all its children:\n%v", string(b)) - } - - if _, err := task.Delete(ctx, WithProcessKill); err != nil { - t.Error(err) - } -} - func TestContainerKillInitPidHost(t *testing.T) { initContainerAndCheckChildrenDieOnKill(t, oci.WithHostNamespace(specs.PIDNamespace)) } -func TestContainerKillInitKillsChildWhenNotHostPid(t *testing.T) { - initContainerAndCheckChildrenDieOnKill(t) -} - func TestUserNamespaces(t *testing.T) { t.Parallel() t.Run("WritableRootFS", func(t *testing.T) { testUserNamespaces(t, false) }) @@ -1570,49 +1374,6 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) { } } -func TestTaskResize(t *testing.T) { - t.Parallel() - - client, err := newClient(t, address) - if err != nil { - t.Fatal(err) - } - defer client.Close() - - var ( - image Image - ctx, cancel = testContext(t) - id = t.Name() - ) - defer cancel() - - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } - container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), withExitStatus(7))) - if err != nil { - t.Fatal(err) - } - defer container.Delete(ctx, WithSnapshotCleanup) - - task, err := container.NewTask(ctx, empty()) - if err != nil { - t.Fatal(err) - } - defer task.Delete(ctx) - - statusC, err := task.Wait(ctx) - if err != nil { - t.Fatal(err) - } - if err := task.Resize(ctx, 32, 32); err != nil { - t.Fatal(err) - } - task.Kill(ctx, syscall.SIGKILL) - <-statusC -} - func TestUIDNoGID(t *testing.T) { t.Parallel() @@ -1868,73 +1629,3 @@ func TestShimOOMScore(t *testing.T) { <-statusC } - -func TestTaskSpec(t *testing.T) { - t.Parallel() - - client, err := newClient(t, address) - if err != nil { - t.Fatal(err) - } - defer client.Close() - - var ( - image Image - ctx, cancel = testContext(t) - id = t.Name() - ) - defer cancel() - - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } - - container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), longCommand)) - if err != nil { - t.Fatal(err) - } - defer container.Delete(ctx, WithSnapshotCleanup) - - task, err := container.NewTask(ctx, empty()) - if err != nil { - t.Fatal(err) - } - defer task.Delete(ctx) - - statusC, err := task.Wait(ctx) - if err != nil { - t.Fatal(err) - } - - spec, err := task.Spec(ctx) - if err != nil { - t.Fatal(err) - } - if spec == nil { - t.Fatal("spec from task is nil") - } - direct, err := newDirectIO(ctx, false) - if err != nil { - t.Fatal(err) - } - defer direct.Delete() - - lt, err := container.Task(ctx, direct.IOAttach) - if err != nil { - t.Fatal(err) - } - - spec, err = lt.Spec(ctx) - if err != nil { - t.Fatal(err) - } - if spec == nil { - t.Fatal("spec from loaded task is nil") - } - - if err := task.Kill(ctx, syscall.SIGKILL); err != nil { - t.Fatal(err) - } - <-statusC -} diff --git a/integration/client/container_test.go b/integration/client/container_test.go index 6874e7837..adde09857 100644 --- a/integration/client/container_test.go +++ b/integration/client/container_test.go @@ -54,7 +54,7 @@ func empty() cio.Creator { // TODO (@mlaventure) windows searches for pipes // when none are provided if runtime.GOOS == "windows" { - return cio.NewCreator(cio.WithStdio) + return cio.NewCreator(cio.WithStdio, cio.WithTerminal) } return cio.NullIO } @@ -1941,6 +1941,238 @@ func TestRegressionIssue4769(t *testing.T) { } } +func TestDaemonRestart(t *testing.T) { + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + image Image + ctx, cancel = testContext(t) + id = t.Name() + ) + defer cancel() + + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + + container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), longCommand)) + if err != nil { + t.Fatal(err) + } + defer container.Delete(ctx, WithSnapshotCleanup) + + task, err := container.NewTask(ctx, empty()) + if err != nil { + t.Fatal(err) + } + defer task.Delete(ctx) + + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + if err := task.Start(ctx); err != nil { + t.Fatal(err) + } + + var exitStatus ExitStatus + if err := ctrd.Restart(func() { + exitStatus = <-statusC + }); err != nil { + t.Fatal(err) + } + + if exitStatus.Error() == nil { + t.Errorf(`first task.Wait() should have failed with "transport is closing"`) + } + + waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second) + serving, err := client.IsServing(waitCtx) + waitCancel() + if !serving { + t.Fatalf("containerd did not start within 2s: %v", err) + } + + statusC, err = task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + if err := task.Kill(ctx, syscall.SIGKILL); err != nil { + t.Fatal(err) + } + + <-statusC +} + +type directIO struct { + cio.DirectIO +} + +// ioCreate returns IO available for use with task creation +func (f *directIO) IOCreate(id string) (cio.IO, error) { + return f, nil +} + +// ioAttach returns IO available for use with task attachment +func (f *directIO) IOAttach(set *cio.FIFOSet) (cio.IO, error) { + return f, nil +} + +func (f *directIO) Cancel() { + // nothing to cancel as all operations are handled externally +} + +// Close closes all open fds +func (f *directIO) Close() error { + err := f.Stdin.Close() + if f.Stdout != nil { + if err2 := f.Stdout.Close(); err == nil { + err = err2 + } + } + if f.Stderr != nil { + if err2 := f.Stderr.Close(); err == nil { + err = err2 + } + } + return err +} + +// Delete removes the underlying directory containing fifos +func (f *directIO) Delete() error { + return f.DirectIO.Close() +} + +func initContainerAndCheckChildrenDieOnKill(t *testing.T, opts ...oci.SpecOpts) { + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + image Image + ctx, cancel = testContext(t) + id = t.Name() + ) + defer cancel() + + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + + opts = append(opts, oci.WithImageConfig(image)) + opts = append(opts, longCommand) + + container, err := client.NewContainer(ctx, id, + WithNewSnapshot(id, image), + WithNewSpec(opts...), + ) + if err != nil { + t.Fatal(err) + } + defer container.Delete(ctx, WithSnapshotCleanup) + + stdout := bytes.NewBuffer(nil) + task, err := container.NewTask(ctx, cio.NewCreator(withByteBuffers(stdout))) + if err != nil { + t.Fatal(err) + } + defer task.Delete(ctx) + + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + if err := task.Start(ctx); err != nil { + t.Fatal(err) + } + + if err := task.Kill(ctx, syscall.SIGKILL); err != nil { + t.Error(err) + } + + // Give the shim time to reap the init process and kill the orphans + select { + case <-statusC: + case <-time.After(100 * time.Millisecond): + } + + command := []string{"ps", "ax"} + if runtime.GOOS == "windows" { + command = []string{"tasklist"} + } + b, err := exec.Command(command[0], command[1:]...).CombinedOutput() + if err != nil { + t.Fatal(err) + } + + // The container is using longCommand, which contains sleep 1 on Linux, and ping -t localhost on Windows. + if strings.Contains(string(b), "sleep 1") || strings.Contains(string(b), "ping -t localhost") { + t.Fatalf("killing init didn't kill all its children:\n%v", string(b)) + } + + if _, err := task.Delete(ctx, WithProcessKill); err != nil { + t.Error(err) + } +} + +func TestContainerKillInitKillsChildWhenNotHostPid(t *testing.T) { + initContainerAndCheckChildrenDieOnKill(t) +} + +func TestTaskResize(t *testing.T) { + t.Parallel() + + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + image Image + ctx, cancel = testContext(t) + id = t.Name() + ) + defer cancel() + + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), withExitStatus(7))) + if err != nil { + t.Fatal(err) + } + defer container.Delete(ctx, WithSnapshotCleanup) + + task, err := container.NewTask(ctx, empty()) + if err != nil { + t.Fatal(err) + } + defer task.Delete(ctx) + + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + if err := task.Resize(ctx, 32, 32); err != nil { + t.Fatal(err) + } + task.Kill(ctx, syscall.SIGKILL) + <-statusC +} + func TestContainerImage(t *testing.T) { t.Parallel() @@ -2050,3 +2282,73 @@ func TestContainerNoSTDIN(t *testing.T) { t.Errorf("expected status 0 from wait but received %d", code) } } + +func TestTaskSpec(t *testing.T) { + t.Parallel() + + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + image Image + ctx, cancel = testContext(t) + id = t.Name() + ) + defer cancel() + + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) + } + + container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), longCommand)) + if err != nil { + t.Fatal(err) + } + defer container.Delete(ctx, WithSnapshotCleanup) + + task, err := container.NewTask(ctx, empty()) + if err != nil { + t.Fatal(err) + } + defer task.Delete(ctx) + + statusC, err := task.Wait(ctx) + if err != nil { + t.Fatal(err) + } + + spec, err := task.Spec(ctx) + if err != nil { + t.Fatal(err) + } + if spec == nil { + t.Fatal("spec from task is nil") + } + direct, err := newDirectIO(ctx, false) + if err != nil { + t.Fatal(err) + } + defer direct.Delete() + + lt, err := container.Task(ctx, direct.IOAttach) + if err != nil { + t.Fatal(err) + } + + spec, err = lt.Spec(ctx) + if err != nil { + t.Fatal(err) + } + if spec == nil { + t.Fatal("spec from loaded task is nil") + } + + if err := task.Kill(ctx, syscall.SIGKILL); err != nil { + t.Fatal(err) + } + <-statusC +} diff --git a/integration/client/daemon_config_linux_test.go b/integration/client/daemon_config_linux_test.go index 51a2a0699..45bbc9096 100644 --- a/integration/client/daemon_config_linux_test.go +++ b/integration/client/daemon_config_linux_test.go @@ -18,8 +18,6 @@ package client import ( "bufio" - "bytes" - "context" "fmt" "os" "path/filepath" @@ -31,91 +29,10 @@ import ( "github.com/containerd/cgroups" . "github.com/containerd/containerd" "github.com/containerd/containerd/oci" - "github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/plugin" "github.com/containerd/containerd/runtime/v2/runc/options" - srvconfig "github.com/containerd/containerd/services/server/config" - exec "golang.org/x/sys/execabs" ) -// the following nolint is for shutting up gometalinter on non-linux. -// nolint: unused -func newDaemonWithConfig(t *testing.T, configTOML string) (*Client, *daemon, func()) { - if testing.Short() { - t.Skip() - } - testutil.RequiresRoot(t) - var ( - ctrd = daemon{} - configTOMLDecoded srvconfig.Config - buf = bytes.NewBuffer(nil) - ) - - tempDir, err := os.MkdirTemp("", "containerd-test-new-daemon-with-config") - if err != nil { - t.Fatal(err) - } - defer func() { - if err != nil { - os.RemoveAll(tempDir) - } - }() - - configTOMLFile := filepath.Join(tempDir, "config.toml") - if err = os.WriteFile(configTOMLFile, []byte(configTOML), 0600); err != nil { - t.Fatal(err) - } - - if err = srvconfig.LoadConfig(configTOMLFile, &configTOMLDecoded); err != nil { - t.Fatal(err) - } - - address := configTOMLDecoded.GRPC.Address - if address == "" { - address = filepath.Join(tempDir, "containerd.sock") - } - args := []string{"-c", configTOMLFile} - if configTOMLDecoded.Root == "" { - args = append(args, "--root", filepath.Join(tempDir, "root")) - } - if configTOMLDecoded.State == "" { - args = append(args, "--state", filepath.Join(tempDir, "state")) - } - if err = ctrd.start("containerd", address, args, buf, buf); err != nil { - t.Fatalf("%v: %s", err, buf.String()) - } - - waitCtx, waitCancel := context.WithTimeout(context.TODO(), 2*time.Second) - client, err := ctrd.waitForStart(waitCtx) - waitCancel() - if err != nil { - ctrd.Kill() - ctrd.Wait() - t.Fatalf("%v: %s", err, buf.String()) - } - - cleanup := func() { - if err := client.Close(); err != nil { - t.Fatalf("failed to close client: %v", err) - } - if err := ctrd.Stop(); err != nil { - if err := ctrd.Kill(); err != nil { - t.Fatalf("failed to signal containerd: %v", err) - } - } - if err := ctrd.Wait(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - t.Fatalf("failed to wait for: %v", err) - } - } - if err := os.RemoveAll(tempDir); err != nil { - t.Fatalf("failed to remove %s: %v", tempDir, err) - } - // cleaning config-specific resources is up to the caller - } - return client, &ctrd, cleanup -} - // TestDaemonRuntimeRoot ensures plugin.linux.runtime_root is not ignored func TestDaemonRuntimeRoot(t *testing.T) { runtimeRoot, err := os.MkdirTemp("", "containerd-test-runtime-root") diff --git a/integration/client/daemon_test.go b/integration/client/daemon_test.go index 43f197ac4..a7d67be5f 100644 --- a/integration/client/daemon_test.go +++ b/integration/client/daemon_test.go @@ -19,6 +19,7 @@ package client import ( "context" "io" + "runtime" "sync" "syscall" @@ -110,8 +111,12 @@ func (d *daemon) Restart(stopCb func()) error { return errors.New("daemon is not running") } + signal := syscall.SIGTERM + if runtime.GOOS == "windows" { + signal = syscall.SIGKILL + } var err error - if err = d.cmd.Process.Signal(syscall.SIGTERM); err != nil { + if err = d.cmd.Process.Signal(signal); err != nil { return errors.Wrap(err, "failed to signal daemon") } diff --git a/integration/client/helpers_unix_test.go b/integration/client/helpers_unix_test.go index 586fade9f..5670ade1e 100644 --- a/integration/client/helpers_unix_test.go +++ b/integration/client/helpers_unix_test.go @@ -23,6 +23,7 @@ import ( "context" "fmt" + "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -56,3 +57,15 @@ func withExecExitStatus(s *specs.Process, es int) { func withExecArgs(s *specs.Process, args ...string) { s.Args = args } + +func newDirectIO(ctx context.Context, terminal bool) (*directIO, error) { + fifos, err := cio.NewFIFOSetInDir("", "", terminal) + if err != nil { + return nil, err + } + dio, err := cio.NewDirectIO(ctx, fifos) + if err != nil { + return nil, err + } + return &directIO{DirectIO: *dio}, nil +} diff --git a/integration/client/helpers_windows_test.go b/integration/client/helpers_windows_test.go index 2d8b11d78..3616c091d 100644 --- a/integration/client/helpers_windows_test.go +++ b/integration/client/helpers_windows_test.go @@ -17,9 +17,12 @@ package client import ( + "bytes" "context" + "io" "strconv" + "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -53,3 +56,21 @@ func withExecExitStatus(s *specs.Process, es int) { func withExecArgs(s *specs.Process, args ...string) { s.Args = append([]string{"cmd", "/c"}, args...) } + +type bytesBuffer struct { + *bytes.Buffer +} + +// Close is a noop operation. +func (b bytesBuffer) Close() error { + return nil +} + +func newDirectIO(ctx context.Context, terminal bool) (*directIO, error) { + readb := bytesBuffer{bytes.NewBuffer(nil)} + writeb := io.NopCloser(bytes.NewBuffer(nil)) + errb := io.NopCloser(bytes.NewBuffer(nil)) + + dio := cio.NewDirectIO(readb, writeb, errb, terminal) + return &directIO{DirectIO: *dio}, nil +} diff --git a/integration/client/lease_test.go b/integration/client/lease_test.go index c9c73c4b7..130ee5c0e 100644 --- a/integration/client/lease_test.go +++ b/integration/client/lease_test.go @@ -28,10 +28,6 @@ import ( ) func TestLeaseResources(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - ctx, cancel := testContext(t) defer cancel() @@ -41,11 +37,15 @@ func TestLeaseResources(t *testing.T) { } defer client.Close() + snapshotterName := "native" + if runtime.GOOS == "windows" { + snapshotterName = "windows" + } var ( ls = client.LeasesService() cs = client.ContentStore() imgSrv = client.ImageService() - sn = client.SnapshotService("native") + sn = client.SnapshotService(snapshotterName) ) l, err := ls.Create(ctx, leases.WithRandomID()) @@ -57,7 +57,7 @@ func TestLeaseResources(t *testing.T) { // step 1: download image imageName := "k8s.gcr.io/pause:3.6" - image, err := client.Pull(ctx, imageName, WithPullUnpack, WithPullSnapshotter("native")) + image, err := client.Pull(ctx, imageName, WithPullUnpack, WithPullSnapshotter(snapshotterName)) if err != nil { t.Fatal(err) } @@ -86,7 +86,7 @@ func TestLeaseResources(t *testing.T) { // step 2: reference snapshotter with lease r := leases.Resource{ ID: chainID.String(), - Type: "snapshots/native", + Type: "snapshots/" + snapshotterName, } if err := ls.AddResource(ctx, l, r); err != nil { diff --git a/integration/client/restart_monitor_linux_test.go b/integration/client/restart_monitor_test.go similarity index 56% rename from integration/client/restart_monitor_linux_test.go rename to integration/client/restart_monitor_test.go index cde7eeb20..f17e98dfc 100644 --- a/integration/client/restart_monitor_linux_test.go +++ b/integration/client/restart_monitor_test.go @@ -17,8 +17,12 @@ package client import ( + "bytes" "context" "fmt" + "os" + "path/filepath" + "runtime" "syscall" "testing" "time" @@ -26,8 +30,93 @@ import ( . "github.com/containerd/containerd" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/oci" + "github.com/containerd/containerd/pkg/testutil" + srvconfig "github.com/containerd/containerd/services/server/config" + exec "golang.org/x/sys/execabs" ) +// the following nolint is for shutting up gometalinter on non-linux. +// nolint: unused +func newDaemonWithConfig(t *testing.T, configTOML string) (*Client, *daemon, func()) { + if testing.Short() { + t.Skip() + } + testutil.RequiresRoot(t) + var ( + ctrd = daemon{} + configTOMLDecoded srvconfig.Config + buf = bytes.NewBuffer(nil) + ) + + tempDir, err := os.MkdirTemp("", "containerd-test-new-daemon-with-config") + if err != nil { + t.Fatal(err) + } + defer func() { + if err != nil { + os.RemoveAll(tempDir) + } + }() + + configTOMLFile := filepath.Join(tempDir, "config.toml") + if err = os.WriteFile(configTOMLFile, []byte(configTOML), 0600); err != nil { + t.Fatal(err) + } + + if err = srvconfig.LoadConfig(configTOMLFile, &configTOMLDecoded); err != nil { + t.Fatal(err) + } + + address := configTOMLDecoded.GRPC.Address + if address == "" { + if runtime.GOOS == "windows" { + address = fmt.Sprintf(`\\.\pipe\containerd-containerd-test-%s`, filepath.Base(tempDir)) + } else { + address = filepath.Join(tempDir, "containerd.sock") + } + } + args := []string{"-c", configTOMLFile} + if configTOMLDecoded.Root == "" { + args = append(args, "--root", filepath.Join(tempDir, "root")) + } + if configTOMLDecoded.State == "" { + args = append(args, "--state", filepath.Join(tempDir, "state")) + } + if err = ctrd.start("containerd", address, args, buf, buf); err != nil { + t.Fatalf("%v: %s", err, buf.String()) + } + + waitCtx, waitCancel := context.WithTimeout(context.TODO(), 2*time.Second) + client, err := ctrd.waitForStart(waitCtx) + waitCancel() + if err != nil { + ctrd.Kill() + ctrd.Wait() + t.Fatalf("%v: %s", err, buf.String()) + } + + cleanup := func() { + if err := client.Close(); err != nil { + t.Fatalf("failed to close client: %v", err) + } + if err := ctrd.Stop(); err != nil { + if err := ctrd.Kill(); err != nil { + t.Fatalf("failed to signal containerd: %v", err) + } + } + if err := ctrd.Wait(); err != nil { + if _, ok := err.(*exec.ExitError); !ok { + t.Fatalf("failed to wait for: %v", err) + } + } + if err := os.RemoveAll(tempDir); err != nil { + t.Fatalf("failed to remove %s: %v", tempDir, err) + } + // cleaning config-specific resources is up to the caller + } + return client, &ctrd, cleanup +} + // TestRestartMonitor tests restarting containers // with the restart monitor service plugin func TestRestartMonitor(t *testing.T) { @@ -60,7 +149,7 @@ version = 2 WithNewSnapshot(id, image), WithNewSpec( oci.WithImageConfig(image), - withProcessArgs("sleep", "infinity"), + longCommand, ), withRestartStatus(Running), )