Separate go module for client tests
Will help us drop dependency to github.com/Microsoft/hcsshim/test in the main go.mod Signed-off-by: Davanum Srinivas <davanum@gmail.com>
This commit is contained in:
125
integration/client/benchmark_test.go
Normal file
125
integration/client/benchmark_test.go
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
)
|
||||
|
||||
func BenchmarkContainerCreate(b *testing.B) {
|
||||
client, err := newClient(b, address)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(b)
|
||||
defer cancel()
|
||||
|
||||
image, err := client.GetImage(ctx, testImage)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
spec, err := oci.GenerateSpec(ctx, client, &containers.Container{ID: b.Name()}, oci.WithImageConfig(image), withTrue())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
var containers []Container
|
||||
defer func() {
|
||||
for _, c := range containers {
|
||||
if err := c.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// reset the timer before creating containers
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := fmt.Sprintf("%s-%d", b.Name(), i)
|
||||
container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithSpec(spec))
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
containers = append(containers, container)
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkContainerStart(b *testing.B) {
|
||||
client, err := newClient(b, address)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(b)
|
||||
defer cancel()
|
||||
|
||||
image, err := client.GetImage(ctx, testImage)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
spec, err := oci.GenerateSpec(ctx, client, &containers.Container{ID: b.Name()}, oci.WithImageConfig(image), withTrue())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
var containers []Container
|
||||
defer func() {
|
||||
for _, c := range containers {
|
||||
if err := c.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := fmt.Sprintf("%s-%d", b.Name(), i)
|
||||
container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithSpec(spec))
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
containers = append(containers, container)
|
||||
|
||||
}
|
||||
// reset the timer before starting tasks
|
||||
b.ResetTimer()
|
||||
for _, c := range containers {
|
||||
task, err := c.NewTask(ctx, empty())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
defer task.Delete(ctx)
|
||||
if err := task.Start(ctx); err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
521
integration/client/client_test.go
Normal file
521
integration/client/client_test.go
Normal file
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/defaults"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/log/logtest"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/pkg/testutil"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/sys"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
address string
|
||||
noDaemon bool
|
||||
noCriu bool
|
||||
supportsCriu bool
|
||||
testNamespace = "testing"
|
||||
ctrdStdioFilePath string
|
||||
|
||||
ctrd = &daemon{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&address, "address", defaultAddress, "The address to the containerd socket for use in the tests")
|
||||
flag.BoolVar(&noDaemon, "no-daemon", false, "Do not start a dedicated daemon for the tests")
|
||||
flag.BoolVar(&noCriu, "no-criu", false, "Do not run the checkpoint tests")
|
||||
}
|
||||
|
||||
func testContext(t testing.TB) (context.Context, context.CancelFunc) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
ctx = namespaces.WithNamespace(ctx, testNamespace)
|
||||
if t != nil {
|
||||
ctx = logtest.WithT(ctx, t)
|
||||
}
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if testing.Short() {
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
testutil.RequiresRootM()
|
||||
// check if criu is installed on the system
|
||||
_, err := exec.LookPath("criu")
|
||||
supportsCriu = err == nil && !noCriu
|
||||
|
||||
var (
|
||||
buf = bytes.NewBuffer(nil)
|
||||
ctx, cancel = testContext(nil)
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
if !noDaemon {
|
||||
sys.ForceRemoveAll(defaultRoot)
|
||||
|
||||
stdioFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "could not create a new stdio temp file: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() {
|
||||
stdioFile.Close()
|
||||
os.Remove(stdioFile.Name())
|
||||
}()
|
||||
ctrdStdioFilePath = stdioFile.Name()
|
||||
stdioWriter := io.MultiWriter(stdioFile, buf)
|
||||
|
||||
err = ctrd.start("containerd", address, []string{
|
||||
"--root", defaultRoot,
|
||||
"--state", defaultState,
|
||||
"--log-level", "debug",
|
||||
"--config", createShimDebugConfig(),
|
||||
}, stdioWriter, stdioWriter)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
waitCtx, waitCancel := context.WithTimeout(ctx, 4*time.Second)
|
||||
client, err := ctrd.waitForStart(waitCtx)
|
||||
waitCancel()
|
||||
if err != nil {
|
||||
ctrd.Kill()
|
||||
ctrd.Wait()
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// print out the version in information
|
||||
version, err := client.Version(ctx)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error getting version: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// allow comparison with containerd under test
|
||||
log.G(ctx).WithFields(logrus.Fields{
|
||||
"version": version.Version,
|
||||
"revision": version.Revision,
|
||||
"runtime": os.Getenv("TEST_RUNTIME"),
|
||||
}).Info("running tests against containerd")
|
||||
|
||||
// pull a seed image
|
||||
log.G(ctx).WithField("image", testImage).Info("start to pull seed image")
|
||||
if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
|
||||
ctrd.Kill()
|
||||
ctrd.Wait()
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := client.Close(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to close client", err)
|
||||
}
|
||||
|
||||
// run the test
|
||||
status := m.Run()
|
||||
|
||||
if !noDaemon {
|
||||
// tear down the daemon and resources created
|
||||
if err := ctrd.Stop(); err != nil {
|
||||
if err := ctrd.Kill(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to signal containerd", err)
|
||||
}
|
||||
}
|
||||
if err := ctrd.Wait(); err != nil {
|
||||
if _, ok := err.(*exec.ExitError); !ok {
|
||||
fmt.Fprintln(os.Stderr, "failed to wait for containerd", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := sys.ForceRemoveAll(defaultRoot); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to remove test root dir", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// only print containerd logs if the test failed or tests were run with -v
|
||||
if status != 0 || testing.Verbose() {
|
||||
fmt.Fprintln(os.Stderr, buf.String())
|
||||
}
|
||||
}
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func newClient(t testing.TB, address string, opts ...ClientOpt) (*Client, error) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
if rt := os.Getenv("TEST_RUNTIME"); rt != "" {
|
||||
opts = append(opts, WithDefaultRuntime(rt))
|
||||
}
|
||||
// testutil.RequiresRoot(t) is not needed here (already called in TestMain)
|
||||
return New(address, opts...)
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("New() returned nil client")
|
||||
}
|
||||
if err := client.Close(); err != nil {
|
||||
t.Errorf("client closed returned error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// All the container's tests depends on this, we need it to run first.
|
||||
func TestImagePull(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
_, err = client.Pull(ctx, testImage, WithPlatformMatcher(platforms.Default()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullWithDiscardContent(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
err = client.ImageService().Delete(ctx, testImage, images.SynchronousDelete())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ls := client.LeasesService()
|
||||
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx = leases.WithLease(ctx, l.ID)
|
||||
img, err := client.Pull(ctx, testImage,
|
||||
WithPlatformMatcher(platforms.Default()),
|
||||
WithPullUnpack,
|
||||
WithChildLabelMap(images.ChildGCLabelsFilterLayers),
|
||||
)
|
||||
// Synchronously garbage collect contents
|
||||
if errL := ls.Delete(ctx, l, leases.SynchronousDelete); errL != nil {
|
||||
t.Fatal(errL)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if all layer contents have been unpacked and aren't preserved
|
||||
var (
|
||||
diffIDs []digest.Digest
|
||||
layers []digest.Digest
|
||||
)
|
||||
cs := client.ContentStore()
|
||||
manifest, err := images.Manifest(ctx, cs, img.Target(), platforms.Default())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(manifest.Layers) == 0 {
|
||||
t.Fatalf("failed to get children from %v", img.Target())
|
||||
}
|
||||
for _, l := range manifest.Layers {
|
||||
layers = append(layers, l.Digest)
|
||||
}
|
||||
config, err := images.Config(ctx, cs, img.Target(), platforms.Default())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
diffIDs, err = images.RootFS(ctx, cs, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(layers) != len(diffIDs) {
|
||||
t.Fatalf("number of layers and diffIDs don't match: %d != %d", len(layers), len(diffIDs))
|
||||
} else if len(layers) == 0 {
|
||||
t.Fatalf("there is no layers in the target image(parent: %v)", img.Target())
|
||||
}
|
||||
var (
|
||||
sn = client.SnapshotService("")
|
||||
chain []digest.Digest
|
||||
)
|
||||
for i, dgst := range layers {
|
||||
chain = append(chain, diffIDs[i])
|
||||
chainID := identity.ChainID(chain).String()
|
||||
if _, err := sn.Stat(ctx, chainID); err != nil {
|
||||
t.Errorf("snapshot %v must exist: %v", chainID, err)
|
||||
}
|
||||
if _, err := cs.Info(ctx, dgst); err == nil || !errdefs.IsNotFound(err) {
|
||||
t.Errorf("content %v must be garbage collected: %v", dgst, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullAllPlatforms(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
cs := client.ContentStore()
|
||||
img, err := client.Fetch(ctx, "docker.io/library/busybox:latest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
index := img.Target
|
||||
manifests, err := images.Children(ctx, cs, index)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, manifest := range manifests {
|
||||
children, err := images.Children(ctx, cs, manifest)
|
||||
if err != nil {
|
||||
t.Fatal("Th")
|
||||
}
|
||||
// check if childless data type has blob in content store
|
||||
for _, desc := range children {
|
||||
ra, err := cs.ReaderAt(ctx, desc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ra.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullSomePlatforms(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
cs := client.ContentStore()
|
||||
platformList := []string{"linux/amd64", "linux/arm64/v8", "linux/s390x"}
|
||||
m := make(map[string]platforms.Matcher)
|
||||
var opts []RemoteOpt
|
||||
|
||||
for _, platform := range platformList {
|
||||
p, err := platforms.Parse(platform)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m[platform] = platforms.NewMatcher(p)
|
||||
opts = append(opts, WithPlatform(platform))
|
||||
}
|
||||
|
||||
img, err := client.Fetch(ctx, "k8s.gcr.io/pause:3.4.1", opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
index := img.Target
|
||||
manifests, err := images.Children(ctx, cs, index)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
count := 0
|
||||
for _, manifest := range manifests {
|
||||
children, err := images.Children(ctx, cs, manifest)
|
||||
|
||||
found := false
|
||||
for _, matcher := range m {
|
||||
if manifest.Platform == nil {
|
||||
t.Fatal("manifest should have proper platform")
|
||||
}
|
||||
if matcher.Match(*manifest.Platform) {
|
||||
count++
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
if len(children) == 0 {
|
||||
t.Fatal("manifest should have pulled children content")
|
||||
}
|
||||
|
||||
// check if childless data type has blob in content store
|
||||
for _, desc := range children {
|
||||
ra, err := cs.ReaderAt(ctx, desc)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ra.Close()
|
||||
}
|
||||
} else if err == nil {
|
||||
t.Fatal("manifest should not have pulled children content")
|
||||
}
|
||||
}
|
||||
|
||||
if count != len(platformList) {
|
||||
t.Fatal("expected a different number of pulled manifests")
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullSchema1(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
schema1TestImage := "gcr.io/google_containers/pause:3.0@sha256:0d093c962a6c2dd8bb8727b661e2b5f13e9df884af9945b4cc7088d9350cd3ee"
|
||||
_, err = client.Pull(ctx, schema1TestImage, WithPlatform(platforms.DefaultString()), WithSchema1Conversion)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullWithConcurrencyLimit(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
_, err = client.Pull(ctx, testImage,
|
||||
WithPlatformMatcher(platforms.Default()),
|
||||
WithMaxConcurrentDownloads(2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientReconnect(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client == nil {
|
||||
t.Fatal("New() returned nil client")
|
||||
}
|
||||
ok, err := client.IsServing(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("containerd is not serving")
|
||||
}
|
||||
if err := client.Reconnect(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ok, err = client.IsServing(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("containerd is not serving")
|
||||
}
|
||||
if err := client.Close(); err != nil {
|
||||
t.Errorf("client closed returned error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func createShimDebugConfig() string {
|
||||
f, err := ioutil.TempFile("", "containerd-config-")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create config file: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.WriteString("version = 1\n"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := f.WriteString("[plugins.linux]\n\tshim_debug = true\n"); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return f.Name()
|
||||
}
|
||||
|
||||
func TestDefaultRuntimeWithNamespaceLabels(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
namespaces := client.NamespaceService()
|
||||
testRuntime := "testRuntime"
|
||||
runtimeLabel := defaults.DefaultRuntimeNSLabel
|
||||
if err := namespaces.SetLabel(ctx, testNamespace, runtimeLabel, testRuntime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testClient, err := New(address, WithDefaultNamespace(testNamespace))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer testClient.Close()
|
||||
if testClient.Runtime() != testRuntime {
|
||||
t.Error("failed to set default runtime from namespace labels")
|
||||
}
|
||||
}
|
||||
80
integration/client/client_ttrpc_test.go
Normal file
80
integration/client/client_ttrpc_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v1 "github.com/containerd/containerd/api/services/ttrpc/events/v1"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/pkg/ttrpcutil"
|
||||
"github.com/containerd/ttrpc"
|
||||
"github.com/gogo/protobuf/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestClientTTRPC_New(t *testing.T) {
|
||||
client, err := ttrpcutil.NewClient(address + ".ttrpc")
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = client.Close()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestClientTTRPC_Reconnect(t *testing.T) {
|
||||
client, err := ttrpcutil.NewClient(address + ".ttrpc")
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = client.Reconnect()
|
||||
assert.NilError(t, err)
|
||||
|
||||
service, err := client.EventsService()
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Send test request to make sure its alive after reconnect
|
||||
_, err = service.Forward(context.Background(), &v1.ForwardRequest{
|
||||
Envelope: &v1.Envelope{
|
||||
Timestamp: time.Now(),
|
||||
Namespace: namespaces.Default,
|
||||
Topic: "/test",
|
||||
Event: &types.Any{},
|
||||
},
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = client.Close()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
func TestClientTTRPC_Close(t *testing.T) {
|
||||
client, err := ttrpcutil.NewClient(address + ".ttrpc")
|
||||
assert.NilError(t, err)
|
||||
|
||||
service, err := client.EventsService()
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = client.Close()
|
||||
assert.NilError(t, err)
|
||||
|
||||
_, err = service.Forward(context.Background(), &v1.ForwardRequest{Envelope: &v1.Envelope{}})
|
||||
assert.Equal(t, err, ttrpc.ErrClosed)
|
||||
|
||||
err = client.Close()
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
73
integration/client/client_unix_test.go
Normal file
73
integration/client/client_unix_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRoot = "/var/lib/containerd-test"
|
||||
defaultState = "/run/containerd-test"
|
||||
defaultAddress = "/run/containerd-test/containerd.sock"
|
||||
)
|
||||
|
||||
var (
|
||||
testImage string
|
||||
shortCommand = withProcessArgs("true")
|
||||
longCommand = withProcessArgs("/bin/sh", "-c", "while true; do sleep 1; done")
|
||||
)
|
||||
|
||||
func init() {
|
||||
switch runtime.GOARCH {
|
||||
case "386":
|
||||
testImage = "docker.io/i386/alpine:latest"
|
||||
case "arm":
|
||||
testImage = "docker.io/arm32v6/alpine:latest"
|
||||
case "arm64":
|
||||
testImage = "docker.io/arm64v8/alpine:latest"
|
||||
case "ppc64le":
|
||||
testImage = "docker.io/ppc64le/alpine:latest"
|
||||
case "s390x":
|
||||
testImage = "docker.io/s390x/alpine:latest"
|
||||
default:
|
||||
testImage = "docker.io/library/alpine:latest"
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullSchema1WithEmptyLayers(t *testing.T) {
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
schema1TestImageWithEmptyLayers := "gcr.io/google-containers/busybox@sha256:d8d3bc2c183ed2f9f10e7258f84971202325ee6011ba137112e01e30f206de67"
|
||||
_, err = client.Pull(ctx, schema1TestImageWithEmptyLayers, WithPlatform(platforms.DefaultString()), WithSchema1Conversion, WithPullUnpack)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
65
integration/client/client_windows_test.go
Normal file
65
integration/client/client_windows_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
_ "github.com/Microsoft/hcsshim/test/functional/manifest" // For rsrc_amd64.syso
|
||||
)
|
||||
|
||||
const (
|
||||
defaultAddress = `\\.\pipe\containerd-containerd-test`
|
||||
)
|
||||
|
||||
var (
|
||||
defaultRoot = filepath.Join(os.Getenv("programfiles"), "containerd", "root-test")
|
||||
defaultState = filepath.Join(os.Getenv("programfiles"), "containerd", "state-test")
|
||||
testImage string
|
||||
shortCommand = withTrue()
|
||||
longCommand = withProcessArgs("ping", "-t", "localhost")
|
||||
)
|
||||
|
||||
func init() {
|
||||
b := osversion.Build()
|
||||
switch b {
|
||||
case osversion.RS1:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:sac2016"
|
||||
case osversion.RS3:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:1709"
|
||||
case osversion.RS4:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:1803"
|
||||
case osversion.RS5:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:1809"
|
||||
case osversion.V19H1:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:1903"
|
||||
case osversion.V19H2:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:1909"
|
||||
case osversion.V20H1:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:2004"
|
||||
case osversion.V20H2:
|
||||
testImage = "mcr.microsoft.com/windows/nanoserver:20H2"
|
||||
default:
|
||||
fmt.Println("No test image defined for Windows build version:", b)
|
||||
panic("No windows test image found for this Windows build")
|
||||
}
|
||||
|
||||
fmt.Println("Windows test image:", testImage, ", Windows build version:", b)
|
||||
}
|
||||
535
integration/client/container_checkpoint_test.go
Normal file
535
integration/client/container_checkpoint_test.go
Normal file
@@ -0,0 +1,535 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cio"
|
||||
"github.com/containerd/containerd/oci"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
)
|
||||
|
||||
const (
|
||||
testCheckpointName = "checkpoint-test:latest"
|
||||
)
|
||||
|
||||
func TestCheckpointRestorePTY(t *testing.T) {
|
||||
if !supportsCriu {
|
||||
t.Skip("system does not have criu installed")
|
||||
}
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
if client.Runtime() == plugin.RuntimeLinuxV1 {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
var (
|
||||
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),
|
||||
oci.WithProcessArgs("sh", "-c", "read A; echo z${A}z"),
|
||||
oci.WithTTY),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer container.Delete(ctx, WithSnapshotCleanup)
|
||||
|
||||
direct, err := newDirectIO(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer direct.Delete()
|
||||
|
||||
task, err := container.NewTask(ctx, direct.IOCreate)
|
||||
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)
|
||||
}
|
||||
|
||||
checkpoint, err := container.Checkpoint(ctx, testCheckpointName+"withpty", []CheckpointOpts{
|
||||
WithCheckpointRuntime,
|
||||
WithCheckpointRW,
|
||||
WithCheckpointTaskExit,
|
||||
WithCheckpointTask,
|
||||
}...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-statusC
|
||||
|
||||
if _, err := task.Delete(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
direct.Delete()
|
||||
if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
direct, err = newDirectIO(ctx, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
buf = bytes.NewBuffer(nil)
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
io.Copy(buf, direct.Stdout)
|
||||
}()
|
||||
|
||||
if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{
|
||||
WithRestoreImage,
|
||||
WithRestoreSpec,
|
||||
WithRestoreRuntime,
|
||||
WithRestoreRW,
|
||||
}...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if task, err = container.NewTask(ctx, direct.IOCreate,
|
||||
WithTaskCheckpoint(checkpoint)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
statusC, err = task.Wait(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := task.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
direct.Stdin.Write([]byte("hello\n"))
|
||||
<-statusC
|
||||
wg.Wait()
|
||||
|
||||
if err := direct.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
out := buf.String()
|
||||
if !strings.Contains(fmt.Sprintf("%#q", out), `zhelloz`) {
|
||||
t.Fatalf(`expected \x00 in output: %s`, out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckpointRestore(t *testing.T) {
|
||||
if !supportsCriu {
|
||||
t.Skip("system does not have criu installed")
|
||||
}
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
if client.Runtime() == plugin.RuntimeLinuxV1 {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
var (
|
||||
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), oci.WithProcessArgs("sleep", "10")))
|
||||
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)
|
||||
}
|
||||
|
||||
checkpoint, err := container.Checkpoint(ctx, testCheckpointName+"restore", []CheckpointOpts{
|
||||
WithCheckpointRuntime,
|
||||
WithCheckpointRW,
|
||||
WithCheckpointTask,
|
||||
}...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-statusC
|
||||
|
||||
if _, err := task.Delete(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{
|
||||
WithRestoreImage,
|
||||
WithRestoreSpec,
|
||||
WithRestoreRuntime,
|
||||
WithRestoreRW,
|
||||
}...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if task, err = container.NewTask(ctx, empty(), WithTaskCheckpoint(checkpoint)); 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.Fatal(err)
|
||||
}
|
||||
<-statusC
|
||||
}
|
||||
|
||||
func TestCheckpointRestoreNewContainer(t *testing.T) {
|
||||
if !supportsCriu {
|
||||
t.Skip("system does not have criu installed")
|
||||
}
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
if client.Runtime() == plugin.RuntimeLinuxV1 {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
id := t.Name()
|
||||
ctx, cancel := testContext(t)
|
||||
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), oci.WithProcessArgs("sleep", "5")))
|
||||
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)
|
||||
}
|
||||
|
||||
checkpoint, err := container.Checkpoint(ctx, testCheckpointName+"newcontainer", []CheckpointOpts{
|
||||
WithCheckpointRuntime,
|
||||
WithCheckpointRW,
|
||||
WithCheckpointTask,
|
||||
}...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-statusC
|
||||
|
||||
if _, err := task.Delete(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{
|
||||
WithRestoreImage,
|
||||
WithRestoreSpec,
|
||||
WithRestoreRuntime,
|
||||
WithRestoreRW,
|
||||
}...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if task, err = container.NewTask(ctx, empty(), WithTaskCheckpoint(checkpoint)); 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.Fatal(err)
|
||||
}
|
||||
<-statusC
|
||||
}
|
||||
|
||||
func TestCheckpointLeaveRunning(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
if !supportsCriu {
|
||||
t.Skip("system does not have criu installed")
|
||||
}
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
if client.Runtime() == plugin.RuntimeLinuxV1 {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
var (
|
||||
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), oci.WithProcessArgs("sleep", "100")))
|
||||
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)
|
||||
}
|
||||
|
||||
// checkpoint
|
||||
if _, err := container.Checkpoint(ctx, testCheckpointName+"leaverunning", []CheckpointOpts{
|
||||
WithCheckpointRuntime,
|
||||
WithCheckpointRW,
|
||||
WithCheckpointTask,
|
||||
}...); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
status, err := task.Status(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status.Status != Running {
|
||||
t.Fatalf("expected status %q but received %q", Running, status)
|
||||
}
|
||||
|
||||
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
<-statusC
|
||||
}
|
||||
|
||||
func TestCRWithImagePath(t *testing.T) {
|
||||
if !supportsCriu {
|
||||
t.Skip("system does not have criu installed")
|
||||
}
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var (
|
||||
ctx, cancel = testContext(t)
|
||||
id = t.Name() + "-checkpoint"
|
||||
)
|
||||
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), oci.WithProcessArgs("top")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer container.Delete(ctx, WithSnapshotCleanup)
|
||||
|
||||
task, err := container.NewTask(ctx, empty())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statusC, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := task.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create image path store criu image files
|
||||
crDir, err := ioutil.TempDir("", "test-cr")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(crDir)
|
||||
imagePath := filepath.Join(crDir, "cr")
|
||||
// checkpoint task
|
||||
if _, err := task.Checkpoint(ctx, WithCheckpointImagePath(imagePath)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
<-statusC
|
||||
task.Delete(ctx)
|
||||
|
||||
// check image files have been dumped into image path
|
||||
if files, err := ioutil.ReadDir(imagePath); err != nil || len(files) == 0 {
|
||||
t.Fatal("failed to checkpoint with image path set")
|
||||
}
|
||||
|
||||
// restore task with same container image and checkpoint directory,
|
||||
// the restore process should finish in millisecond level
|
||||
id = t.Name() + "-restore"
|
||||
ncontainer, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ncontainer.Delete(ctx, WithSnapshotCleanup)
|
||||
|
||||
ntask, err := ncontainer.NewTask(ctx, empty(), WithRestoreImagePath(imagePath))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statusC, err = ntask.Wait(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ntask.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check top process is existed in restored container
|
||||
spec, err := container.Spec(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stdout := bytes.NewBuffer(nil)
|
||||
spec.Process.Args = []string{"ps", "-ef"}
|
||||
process, err := ntask.Exec(ctx, t.Name()+"_exec", spec.Process, cio.NewCreator(withByteBuffers(stdout)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
processStatusC, err := process.Wait(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := process.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
<-processStatusC
|
||||
if _, err := process.Delete(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(stdout.String(), "top") {
|
||||
t.Errorf("except top process exists in restored container but not, got output %s", stdout.String())
|
||||
}
|
||||
|
||||
// we wrote the same thing after attach
|
||||
if err := ntask.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
<-statusC
|
||||
ntask.Delete(ctx)
|
||||
}
|
||||
2161
integration/client/container_linux_test.go
Normal file
2161
integration/client/container_linux_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1836
integration/client/container_test.go
Normal file
1836
integration/client/container_test.go
Normal file
File diff suppressed because it is too large
Load Diff
89
integration/client/content_test.go
Normal file
89
integration/client/content_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/content/testsuite"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func newContentStore(ctx context.Context, root string) (context.Context, content.Store, func() error, error) {
|
||||
client, err := New(address)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
count uint64
|
||||
cs = client.ContentStore()
|
||||
name = testsuite.Name(ctx)
|
||||
)
|
||||
|
||||
wrap := func(ctx context.Context) (context.Context, func(context.Context) error, error) {
|
||||
n := atomic.AddUint64(&count, 1)
|
||||
ctx = namespaces.WithNamespace(ctx, fmt.Sprintf("%s-n%d", name, n))
|
||||
return client.WithLease(ctx)
|
||||
}
|
||||
|
||||
ctx = testsuite.SetContextWrapper(ctx, wrap)
|
||||
|
||||
return ctx, cs, func() error {
|
||||
for i := uint64(1); i <= count; i++ {
|
||||
ctx = namespaces.WithNamespace(ctx, fmt.Sprintf("%s-n%d", name, i))
|
||||
statuses, err := cs.ListStatuses(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, st := range statuses {
|
||||
if err := cs.Abort(ctx, st.Ref); err != nil && !errdefs.IsNotFound(err) {
|
||||
return errors.Wrapf(err, "failed to abort %s", st.Ref)
|
||||
}
|
||||
}
|
||||
err = cs.Walk(ctx, func(info content.Info) error {
|
||||
if err := cs.Delete(ctx, info.Digest); err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestContentClient(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
testsuite.ContentSuite(t, "ContentClient", newContentStore)
|
||||
}
|
||||
89
integration/client/convert_test.go
Normal file
89
integration/client/convert_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/images/converter"
|
||||
"github.com/containerd/containerd/images/converter/uncompress"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// TestConvert creates an image from testImage, with the following conversion:
|
||||
// - Media type: Docker -> OCI
|
||||
// - Layer type: tar.gz -> tar
|
||||
// - Arch: Multi -> Single
|
||||
func TestConvert(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := New(address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.Fetch(ctx, testImage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dstRef := testImage + "-testconvert"
|
||||
defPlat := platforms.DefaultStrict()
|
||||
opts := []converter.Opt{
|
||||
converter.WithDockerToOCI(true),
|
||||
converter.WithLayerConvertFunc(uncompress.LayerConvertFunc),
|
||||
converter.WithPlatform(defPlat),
|
||||
}
|
||||
dstImg, err := converter.Convert(ctx, client, dstRef, testImage, opts...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if deleteErr := client.ImageService().Delete(ctx, dstRef); deleteErr != nil {
|
||||
t.Fatal(deleteErr)
|
||||
}
|
||||
}()
|
||||
cs := client.ContentStore()
|
||||
plats, err := images.Platforms(ctx, cs, dstImg.Target)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Assert that the image does not have any extra arch.
|
||||
assert.Equal(t, 1, len(plats))
|
||||
assert.Check(t, defPlat.Match(plats[0]))
|
||||
|
||||
// Assert that the media type is converted to OCI and also uncompressed
|
||||
mani, err := images.Manifest(ctx, cs, dstImg.Target, defPlat)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, l := range mani.Layers {
|
||||
if plats[0].OS == "windows" {
|
||||
assert.Equal(t, ocispec.MediaTypeImageLayerNonDistributable, l.MediaType)
|
||||
} else {
|
||||
assert.Equal(t, ocispec.MediaTypeImageLayer, l.MediaType)
|
||||
}
|
||||
}
|
||||
}
|
||||
268
integration/client/daemon_config_linux_test.go
Normal file
268
integration/client/daemon_config_linux_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/cgroups"
|
||||
"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"
|
||||
)
|
||||
|
||||
// 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 := ioutil.TempDir("", "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 = ioutil.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 := ioutil.TempDir("", "containerd-test-runtime-root")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.RemoveAll(runtimeRoot)
|
||||
}
|
||||
}()
|
||||
configTOML := `
|
||||
version = 1
|
||||
[plugins]
|
||||
[plugins.cri]
|
||||
stream_server_port = "0"
|
||||
`
|
||||
|
||||
client, _, cleanup := newDaemonWithConfig(t, configTOML)
|
||||
defer cleanup()
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
// FIXME(AkihiroSuda): import locally frozen image?
|
||||
image, err := client.Pull(ctx, testImage, WithPullUnpack)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
id := t.Name()
|
||||
container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), withProcessArgs("top")), WithRuntime(plugin.RuntimeRuncV1, &options.Options{
|
||||
Root: runtimeRoot,
|
||||
}))
|
||||
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)
|
||||
|
||||
status, err := task.Wait(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
containerPath := filepath.Join(runtimeRoot, testNamespace, id)
|
||||
if _, err = os.Stat(containerPath); err != nil {
|
||||
t.Errorf("error while getting stat for %s: %v", containerPath, err)
|
||||
}
|
||||
|
||||
if err = task.Kill(ctx, syscall.SIGKILL); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
<-status
|
||||
}
|
||||
|
||||
// code most copy from https://github.com/opencontainers/runc
|
||||
func getCgroupPath() (map[string]string, error) {
|
||||
cgroupPath := make(map[string]string)
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
fields := strings.Split(text, " ")
|
||||
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
||||
index := strings.Index(text, " - ")
|
||||
postSeparatorFields := strings.Fields(text[index+3:])
|
||||
numPostFields := len(postSeparatorFields)
|
||||
|
||||
// This is an error as we can't detect if the mount is for "cgroup"
|
||||
if numPostFields == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if postSeparatorFields[0] == "cgroup" {
|
||||
// Check that the mount is properly formatted.
|
||||
if numPostFields < 3 {
|
||||
continue
|
||||
}
|
||||
cgroupPath[filepath.Base(fields[4])] = fields[4]
|
||||
}
|
||||
}
|
||||
|
||||
return cgroupPath, nil
|
||||
}
|
||||
|
||||
// TestDaemonCustomCgroup ensures plugin.cgroup.path is not ignored
|
||||
func TestDaemonCustomCgroup(t *testing.T) {
|
||||
if cgroups.Mode() == cgroups.Unified {
|
||||
t.Skip("test requires cgroup1")
|
||||
}
|
||||
cgroupPath, err := getCgroupPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(cgroupPath) == 0 {
|
||||
t.Skip("skip TestDaemonCustomCgroup since no cgroup path available")
|
||||
}
|
||||
|
||||
customCgroup := fmt.Sprintf("%d", time.Now().Nanosecond())
|
||||
configTOML := `
|
||||
version = 1
|
||||
[cgroup]
|
||||
path = "` + customCgroup + `"`
|
||||
|
||||
_, _, cleanup := newDaemonWithConfig(t, configTOML)
|
||||
|
||||
defer func() {
|
||||
// do cgroup path clean
|
||||
for _, v := range cgroupPath {
|
||||
if _, err := os.Stat(filepath.Join(v, customCgroup)); err == nil {
|
||||
if err := os.RemoveAll(filepath.Join(v, customCgroup)); err != nil {
|
||||
t.Logf("failed to remove cgroup path %s", filepath.Join(v, customCgroup))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
defer cleanup()
|
||||
|
||||
paths := []string{
|
||||
"devices",
|
||||
"memory",
|
||||
"cpu",
|
||||
"blkio",
|
||||
}
|
||||
|
||||
for _, p := range paths {
|
||||
v := cgroupPath[p]
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
path := filepath.Join(v, customCgroup)
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Fatalf("custom cgroup path %s should exist, actually not", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
integration/client/daemon_test.go
Normal file
134
integration/client/daemon_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type daemon struct {
|
||||
sync.Mutex
|
||||
addr string
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func (d *daemon) start(name, address string, args []string, stdout, stderr io.Writer) error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
if d.cmd != nil {
|
||||
return errors.New("daemon is already running")
|
||||
}
|
||||
args = append(args, []string{"--address", address}...)
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd.Wait()
|
||||
return errors.Wrap(err, "failed to start daemon")
|
||||
}
|
||||
d.addr = address
|
||||
d.cmd = cmd
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *daemon) waitForStart(ctx context.Context) (*Client, error) {
|
||||
var (
|
||||
client *Client
|
||||
serving bool
|
||||
err error
|
||||
)
|
||||
|
||||
client, err = New(d.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serving, err = client.IsServing(ctx)
|
||||
if !serving {
|
||||
client.Close()
|
||||
if err == nil {
|
||||
err = errors.New("connection was successful but service is not available")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return client, err
|
||||
}
|
||||
|
||||
func (d *daemon) Stop() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
if d.cmd == nil {
|
||||
return errors.New("daemon is not running")
|
||||
}
|
||||
return d.cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
|
||||
func (d *daemon) Kill() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
if d.cmd == nil {
|
||||
return errors.New("daemon is not running")
|
||||
}
|
||||
return d.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func (d *daemon) Wait() error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
if d.cmd == nil {
|
||||
return errors.New("daemon is not running")
|
||||
}
|
||||
err := d.cmd.Wait()
|
||||
d.cmd = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *daemon) Restart(stopCb func()) error {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
if d.cmd == nil {
|
||||
return errors.New("daemon is not running")
|
||||
}
|
||||
|
||||
var err error
|
||||
if err = d.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||
return errors.Wrap(err, "failed to signal daemon")
|
||||
}
|
||||
|
||||
d.cmd.Wait()
|
||||
|
||||
if stopCb != nil {
|
||||
stopCb()
|
||||
}
|
||||
|
||||
cmd := exec.Command(d.cmd.Path, d.cmd.Args[1:]...)
|
||||
cmd.Stdout = d.cmd.Stdout
|
||||
cmd.Stderr = d.cmd.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
cmd.Wait()
|
||||
return errors.Wrap(err, "failed to start new daemon instance")
|
||||
}
|
||||
d.cmd = cmd
|
||||
|
||||
return nil
|
||||
}
|
||||
85
integration/client/export_test.go
Normal file
85
integration/client/export_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/images/archive"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
)
|
||||
|
||||
// TestExport exports testImage as a tar stream
|
||||
func TestExport(t *testing.T) {
|
||||
// TODO: support windows
|
||||
if testing.Short() || runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := New(address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.Fetch(ctx, testImage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wb := bytes.NewBuffer(nil)
|
||||
err = client.Export(ctx, wb, archive.WithPlatform(platforms.Default()), archive.WithImage(client.ImageService(), testImage))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertOCITar(t, bytes.NewReader(wb.Bytes()))
|
||||
}
|
||||
|
||||
func assertOCITar(t *testing.T, r io.Reader) {
|
||||
// TODO: add more assertion
|
||||
tr := tar.NewReader(r)
|
||||
foundOCILayout := false
|
||||
foundIndexJSON := false
|
||||
for {
|
||||
h, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
if h.Name == "oci-layout" {
|
||||
foundOCILayout = true
|
||||
}
|
||||
if h.Name == "index.json" {
|
||||
foundIndexJSON = true
|
||||
}
|
||||
}
|
||||
if !foundOCILayout {
|
||||
t.Error("oci-layout not found")
|
||||
}
|
||||
if !foundIndexJSON {
|
||||
t.Error("index.json not found")
|
||||
}
|
||||
}
|
||||
23
integration/client/go.mod
Normal file
23
integration/client/go.mod
Normal file
@@ -0,0 +1,23 @@
|
||||
module github.com/containerd/containerd/integration/client
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/Microsoft/hcsshim v0.8.14
|
||||
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3
|
||||
github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68
|
||||
github.com/containerd/containerd v1.5.0-beta.1
|
||||
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328
|
||||
github.com/containerd/ttrpc v1.0.2
|
||||
github.com/containerd/typeurl v1.0.1
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.8.0
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
|
||||
gotest.tools/v3 v3.0.3
|
||||
)
|
||||
|
||||
replace github.com/containerd/containerd => ../../
|
||||
1007
integration/client/go.sum
Normal file
1007
integration/client/go.sum
Normal file
File diff suppressed because it is too large
Load Diff
57
integration/client/helpers_unix_test.go
Normal file
57
integration/client/helpers_unix_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
const newLine = "\n"
|
||||
|
||||
func withExitStatus(es int) oci.SpecOpts {
|
||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
|
||||
s.Process.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func withProcessArgs(args ...string) oci.SpecOpts {
|
||||
return oci.WithProcessArgs(args...)
|
||||
}
|
||||
|
||||
func withCat() oci.SpecOpts {
|
||||
return oci.WithProcessArgs("cat")
|
||||
}
|
||||
|
||||
func withTrue() oci.SpecOpts {
|
||||
return oci.WithProcessArgs("true")
|
||||
}
|
||||
|
||||
func withExecExitStatus(s *specs.Process, es int) {
|
||||
s.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)}
|
||||
}
|
||||
|
||||
func withExecArgs(s *specs.Process, args ...string) {
|
||||
s.Args = args
|
||||
}
|
||||
57
integration/client/helpers_windows_test.go
Normal file
57
integration/client/helpers_windows_test.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
const newLine = "\r\n"
|
||||
|
||||
func withExitStatus(es int) oci.SpecOpts {
|
||||
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
|
||||
s.Process.Args = []string{"cmd", "/c", "exit", strconv.Itoa(es)}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func withProcessArgs(args ...string) oci.SpecOpts {
|
||||
return oci.WithProcessArgs(append([]string{"cmd", "/c"}, args...)...)
|
||||
}
|
||||
|
||||
func withCat() oci.SpecOpts {
|
||||
return oci.WithProcessArgs("cmd", "/c", "more")
|
||||
}
|
||||
|
||||
func withTrue() oci.SpecOpts {
|
||||
return oci.WithProcessArgs("cmd", "/c")
|
||||
}
|
||||
|
||||
func withExecExitStatus(s *specs.Process, es int) {
|
||||
s.Args = []string{"cmd", "/c", "exit", strconv.Itoa(es)}
|
||||
}
|
||||
|
||||
func withExecArgs(s *specs.Process, args ...string) {
|
||||
s.Args = append([]string{"cmd", "/c"}, args...)
|
||||
}
|
||||
275
integration/client/image_test.go
Normal file
275
integration/client/image_test.go
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func TestImageIsUnpacked(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
const imageName = "docker.io/library/busybox:latest"
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Cleanup
|
||||
opts := []images.DeleteOpt{images.SynchronousDelete()}
|
||||
err = client.ImageService().Delete(ctx, imageName, opts...)
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// By default pull does not unpack an image
|
||||
image, err := client.Pull(ctx, imageName, WithPlatform("linux/amd64"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check that image is not unpacked
|
||||
unpacked, err := image.IsUnpacked(ctx, DefaultSnapshotter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if unpacked {
|
||||
t.Fatalf("image should not be unpacked")
|
||||
}
|
||||
|
||||
// Check that image is unpacked
|
||||
err = image.Unpack(ctx, DefaultSnapshotter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
unpacked, err = image.IsUnpacked(ctx, DefaultSnapshotter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !unpacked {
|
||||
t.Fatalf("image should be unpacked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestImagePullWithDistSourceLabel(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
var (
|
||||
source = "docker.io"
|
||||
repoName = "library/busybox"
|
||||
tag = "latest"
|
||||
)
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
imageName := fmt.Sprintf("%s/%s:%s", source, repoName, tag)
|
||||
pMatcher := platforms.Default()
|
||||
|
||||
// pull content without unpack and add distribution source label
|
||||
image, err := client.Pull(ctx, imageName, WithPlatformMatcher(pMatcher))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.ImageService().Delete(ctx, imageName)
|
||||
|
||||
cs := client.ContentStore()
|
||||
key := fmt.Sprintf("containerd.io/distribution.source.%s", source)
|
||||
|
||||
// only check the target platform
|
||||
childrenHandler := images.LimitManifests(images.ChildrenHandler(cs), pMatcher, 1)
|
||||
|
||||
checkLabelHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
||||
children, err := childrenHandler(ctx, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := cs.Info(ctx, desc.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check the label
|
||||
if got := info.Labels[key]; !strings.Contains(got, repoName) {
|
||||
return nil, fmt.Errorf("expected to have %s repo name in label, but got %s", repoName, got)
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
|
||||
if err := images.Dispatch(ctx, images.HandlerFunc(checkLabelHandler), nil, image.Target()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageUsage(t *testing.T) {
|
||||
if testing.Short() || runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
imageName := "docker.io/library/busybox:latest"
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Cleanup
|
||||
err = client.ImageService().Delete(ctx, imageName, images.SynchronousDelete())
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testPlatform := platforms.Only(ocispec.Platform{
|
||||
OS: "linux",
|
||||
Architecture: "amd64",
|
||||
})
|
||||
|
||||
// Pull single platform, do not unpack
|
||||
image, err := client.Pull(ctx, imageName, WithPlatformMatcher(testPlatform))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s1, err := image.Usage(ctx, WithUsageManifestLimit(1))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := image.Usage(ctx, WithUsageManifestLimit(0), WithManifestUsage()); err == nil {
|
||||
t.Fatal("expected NotFound with missing manifests")
|
||||
} else if !errdefs.IsNotFound(err) {
|
||||
t.Fatalf("unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
// Pin image name to specific version for future fetches
|
||||
imageName = imageName + "@" + image.Target().Digest.String()
|
||||
defer client.ImageService().Delete(ctx, imageName, images.SynchronousDelete())
|
||||
|
||||
// Fetch single platforms, but all manifests pulled
|
||||
if _, err := client.Fetch(ctx, imageName, WithPlatformMatcher(testPlatform), WithAllMetadata()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s, err := image.Usage(ctx, WithUsageManifestLimit(1)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if s != s1 {
|
||||
t.Fatalf("unexpected usage %d, expected %d", s, s1)
|
||||
}
|
||||
|
||||
s2, err := image.Usage(ctx, WithUsageManifestLimit(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s2 <= s1 {
|
||||
t.Fatalf("Expected larger usage counting all manifests: %d <= %d", s2, s1)
|
||||
}
|
||||
|
||||
s3, err := image.Usage(ctx, WithUsageManifestLimit(0), WithManifestUsage())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s3 <= s2 {
|
||||
t.Fatalf("Expected larger usage counting all manifest reported sizes: %d <= %d", s3, s2)
|
||||
}
|
||||
|
||||
// Fetch everything
|
||||
if _, err = client.Fetch(ctx, imageName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s, err := image.Usage(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if s != s3 {
|
||||
t.Fatalf("Expected actual usage to equal manifest reported usage of %d: got %d", s3, s)
|
||||
}
|
||||
|
||||
err = image.Unpack(ctx, DefaultSnapshotter)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if s, err := image.Usage(ctx, WithSnapshotUsage()); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if s <= s3 {
|
||||
t.Fatalf("Expected actual usage with snapshots to be greater: %d <= %d", s, s3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageSupportedBySnapshotter_Error(t *testing.T) {
|
||||
var unsupportedImage string
|
||||
if runtime.GOOS == "windows" {
|
||||
unsupportedImage = "docker.io/library/busybox:latest"
|
||||
} else {
|
||||
unsupportedImage = "mcr.microsoft.com/windows/nanoserver:1809"
|
||||
}
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Cleanup
|
||||
err = client.ImageService().Delete(ctx, unsupportedImage)
|
||||
if err != nil && !errdefs.IsNotFound(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.Pull(ctx, unsupportedImage,
|
||||
WithSchema1Conversion,
|
||||
WithPlatform(platforms.DefaultString()),
|
||||
WithPullSnapshotter(DefaultSnapshotter),
|
||||
WithPullUnpack,
|
||||
WithUnpackOpts([]UnpackOpt{WithSnapshotterPlatformCheck()}),
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("expected unpacking %s for snapshotter %s to fail", unsupportedImage, DefaultSnapshotter)
|
||||
}
|
||||
}
|
||||
375
integration/client/import_test.go
Normal file
375
integration/client/import_test.go
Normal file
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/archive/compression"
|
||||
"github.com/containerd/containerd/archive/tartest"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/images/archive"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
specs "github.com/opencontainers/image-spec/specs-go"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// TestExportAndImport exports testImage as a tar stream,
|
||||
// and import the tar stream as a new image.
|
||||
func TestExportAndImport(t *testing.T) {
|
||||
// TODO: support windows
|
||||
if testing.Short() || runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := New(address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
_, err = client.Fetch(ctx, testImage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wb := bytes.NewBuffer(nil)
|
||||
err = client.Export(ctx, wb, archive.WithAllPlatforms(), archive.WithImage(client.ImageService(), testImage))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opts := []ImportOpt{
|
||||
WithImageRefTranslator(archive.AddRefPrefix("foo/bar")),
|
||||
}
|
||||
imgrecs, err := client.Import(ctx, bytes.NewReader(wb.Bytes()), opts...)
|
||||
if err != nil {
|
||||
t.Fatalf("Import failed: %+v", err)
|
||||
}
|
||||
|
||||
for _, imgrec := range imgrecs {
|
||||
if imgrec.Name == testImage {
|
||||
continue
|
||||
}
|
||||
err = client.ImageService().Delete(ctx, imgrec.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := New(address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
tc := tartest.TarContext{}
|
||||
|
||||
b1, d1 := createContent(256, 1)
|
||||
empty := []byte("{}")
|
||||
version := []byte("1.0")
|
||||
|
||||
c1, d2 := createConfig()
|
||||
|
||||
m1, d3, expManifest := createManifest(c1, [][]byte{b1})
|
||||
|
||||
provider := client.ContentStore()
|
||||
|
||||
checkManifest := func(ctx context.Context, t *testing.T, d ocispec.Descriptor, expManifest *ocispec.Manifest) {
|
||||
m, err := images.Manifest(ctx, provider, d, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read target blob: %+v", err)
|
||||
}
|
||||
|
||||
if m.Config.Digest != d2 {
|
||||
t.Fatalf("unexpected digest hash %s, expected %s", m.Config.Digest, d2)
|
||||
}
|
||||
|
||||
if len(m.Layers) != 1 {
|
||||
t.Fatalf("expected 1 layer, has %d", len(m.Layers))
|
||||
}
|
||||
|
||||
if m.Layers[0].Digest != d1 {
|
||||
t.Fatalf("unexpected layer hash %s, expected %s", m.Layers[0].Digest, d1)
|
||||
}
|
||||
|
||||
if expManifest != nil {
|
||||
if !reflect.DeepEqual(m.Layers, expManifest.Layers) {
|
||||
t.Fatalf("DeepEqual on Layers failed: %v vs. %v", m.Layers, expManifest.Layers)
|
||||
}
|
||||
if !reflect.DeepEqual(m.Config, expManifest.Config) {
|
||||
t.Fatalf("DeepEqual on Config failed: %v vs. %v", m.Config, expManifest.Config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
Name string
|
||||
Writer tartest.WriterToTar
|
||||
Check func(*testing.T, []images.Image)
|
||||
Opts []ImportOpt
|
||||
}{
|
||||
{
|
||||
Name: "DockerV2.0",
|
||||
Writer: tartest.TarAll(
|
||||
tc.Dir("bd765cd43e95212f7aa2cab51d0a", 0755),
|
||||
tc.File("bd765cd43e95212f7aa2cab51d0a/json", empty, 0644),
|
||||
tc.File("bd765cd43e95212f7aa2cab51d0a/layer.tar", b1, 0644),
|
||||
tc.File("bd765cd43e95212f7aa2cab51d0a/VERSION", version, 0644),
|
||||
tc.File("repositories", []byte(`{"any":{"1":"bd765cd43e95212f7aa2cab51d0a"}}`), 0644),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "DockerV2.1",
|
||||
Writer: tartest.TarAll(
|
||||
tc.Dir("bd765cd43e95212f7aa2cab51d0a", 0755),
|
||||
tc.File("bd765cd43e95212f7aa2cab51d0a/json", empty, 0644),
|
||||
tc.File("bd765cd43e95212f7aa2cab51d0a/layer.tar", b1, 0644),
|
||||
tc.File("bd765cd43e95212f7aa2cab51d0a/VERSION", version, 0644),
|
||||
tc.File("e95212f7aa2cab51d0abd765cd43.json", c1, 0644),
|
||||
tc.File("manifest.json", []byte(`[{"Config":"e95212f7aa2cab51d0abd765cd43.json","RepoTags":["test-import:notlatest", "another/repo:tag"],"Layers":["bd765cd43e95212f7aa2cab51d0a/layer.tar"]}]`), 0644),
|
||||
),
|
||||
Check: func(t *testing.T, imgs []images.Image) {
|
||||
if len(imgs) == 0 {
|
||||
t.Fatalf("no images")
|
||||
}
|
||||
|
||||
names := []string{
|
||||
"docker.io/library/test-import:notlatest",
|
||||
"docker.io/another/repo:tag",
|
||||
}
|
||||
|
||||
checkImages(t, imgs[0].Target.Digest, imgs, names...)
|
||||
checkManifest(ctx, t, imgs[0].Target, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OCI-BadFormat",
|
||||
Writer: tartest.TarAll(
|
||||
tc.File("oci-layout", []byte(`{"imageLayoutVersion":"2.0.0"}`), 0644),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "OCI",
|
||||
Writer: tartest.TarAll(
|
||||
tc.Dir("blobs", 0755),
|
||||
tc.Dir("blobs/sha256", 0755),
|
||||
tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644),
|
||||
tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644),
|
||||
tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644),
|
||||
tc.File("index.json", createIndex(m1, "latest", "docker.io/lib/img:ok"), 0644),
|
||||
tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644),
|
||||
),
|
||||
Check: func(t *testing.T, imgs []images.Image) {
|
||||
names := []string{
|
||||
"latest",
|
||||
"docker.io/lib/img:ok",
|
||||
}
|
||||
|
||||
checkImages(t, d3, imgs, names...)
|
||||
checkManifest(ctx, t, imgs[0].Target, expManifest)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OCIPrefixName",
|
||||
Writer: tartest.TarAll(
|
||||
tc.Dir("blobs", 0755),
|
||||
tc.Dir("blobs/sha256", 0755),
|
||||
tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644),
|
||||
tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644),
|
||||
tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644),
|
||||
tc.File("index.json", createIndex(m1, "latest", "docker.io/lib/img:ok"), 0644),
|
||||
tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644),
|
||||
),
|
||||
Check: func(t *testing.T, imgs []images.Image) {
|
||||
names := []string{
|
||||
"localhost:5000/myimage:latest",
|
||||
"docker.io/lib/img:ok",
|
||||
}
|
||||
|
||||
checkImages(t, d3, imgs, names...)
|
||||
checkManifest(ctx, t, imgs[0].Target, expManifest)
|
||||
},
|
||||
Opts: []ImportOpt{
|
||||
WithImageRefTranslator(archive.AddRefPrefix("localhost:5000/myimage")),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "OCIPrefixName2",
|
||||
Writer: tartest.TarAll(
|
||||
tc.Dir("blobs", 0755),
|
||||
tc.Dir("blobs/sha256", 0755),
|
||||
tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644),
|
||||
tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644),
|
||||
tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644),
|
||||
tc.File("index.json", createIndex(m1, "latest", "localhost:5000/myimage:old", "docker.io/lib/img:ok"), 0644),
|
||||
tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644),
|
||||
),
|
||||
Check: func(t *testing.T, imgs []images.Image) {
|
||||
names := []string{
|
||||
"localhost:5000/myimage:latest",
|
||||
"localhost:5000/myimage:old",
|
||||
}
|
||||
|
||||
checkImages(t, d3, imgs, names...)
|
||||
checkManifest(ctx, t, imgs[0].Target, expManifest)
|
||||
},
|
||||
Opts: []ImportOpt{
|
||||
WithImageRefTranslator(archive.FilterRefPrefix("localhost:5000/myimage")),
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
images, err := client.Import(ctx, tartest.TarFromWriterTo(tc.Writer), tc.Opts...)
|
||||
if err != nil {
|
||||
if tc.Check != nil {
|
||||
t.Errorf("unexpected import error: %+v", err)
|
||||
}
|
||||
return
|
||||
} else if tc.Check == nil {
|
||||
t.Fatalf("expected error on import")
|
||||
}
|
||||
|
||||
tc.Check(t, images)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func checkImages(t *testing.T, target digest.Digest, actual []images.Image, names ...string) {
|
||||
if len(names) != len(actual) {
|
||||
t.Fatalf("expected %d images, got %d", len(names), len(actual))
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
if actual[i].Target.Digest != target {
|
||||
t.Fatalf("image(%d) unexpected target %s, expected %s", i, actual[i].Target.Digest, target)
|
||||
}
|
||||
if actual[i].Name != n {
|
||||
t.Fatalf("image(%d) unexpected name %q, expected %q", i, actual[i].Name, n)
|
||||
}
|
||||
|
||||
if actual[i].Target.MediaType != ocispec.MediaTypeImageManifest &&
|
||||
actual[i].Target.MediaType != images.MediaTypeDockerSchema2Manifest {
|
||||
t.Fatalf("image(%d) unexpected media type: %s", i, actual[i].Target.MediaType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createContent(size int64, seed int64) ([]byte, digest.Digest) {
|
||||
b, err := ioutil.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), size))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
wb := bytes.NewBuffer(nil)
|
||||
cw, err := compression.CompressStream(wb, compression.Gzip)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := cw.Write(b); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b = wb.Bytes()
|
||||
return b, digest.FromBytes(b)
|
||||
}
|
||||
|
||||
func createConfig() ([]byte, digest.Digest) {
|
||||
image := ocispec.Image{
|
||||
OS: "any",
|
||||
Architecture: "any",
|
||||
Author: "test",
|
||||
}
|
||||
b, _ := json.Marshal(image)
|
||||
|
||||
return b, digest.FromBytes(b)
|
||||
}
|
||||
|
||||
func createManifest(config []byte, layers [][]byte) ([]byte, digest.Digest, *ocispec.Manifest) {
|
||||
manifest := ocispec.Manifest{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
Config: ocispec.Descriptor{
|
||||
MediaType: ocispec.MediaTypeImageConfig,
|
||||
Digest: digest.FromBytes(config),
|
||||
Size: int64(len(config)),
|
||||
Annotations: map[string]string{
|
||||
"ocispec": "manifest.config.descriptor",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, l := range layers {
|
||||
manifest.Layers = append(manifest.Layers, ocispec.Descriptor{
|
||||
MediaType: ocispec.MediaTypeImageLayer,
|
||||
Digest: digest.FromBytes(l),
|
||||
Size: int64(len(l)),
|
||||
Annotations: map[string]string{
|
||||
"ocispec": "manifest.layers.descriptor",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(manifest)
|
||||
|
||||
return b, digest.FromBytes(b), &manifest
|
||||
}
|
||||
|
||||
func createIndex(manifest []byte, tags ...string) []byte {
|
||||
idx := ocispec.Index{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
}
|
||||
d := ocispec.Descriptor{
|
||||
MediaType: ocispec.MediaTypeImageManifest,
|
||||
Digest: digest.FromBytes(manifest),
|
||||
Size: int64(len(manifest)),
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
idx.Manifests = append(idx.Manifests, d)
|
||||
} else {
|
||||
for _, t := range tags {
|
||||
dt := d
|
||||
dt.Annotations = map[string]string{
|
||||
ocispec.AnnotationRefName: t,
|
||||
}
|
||||
idx.Manifests = append(idx.Manifests, dt)
|
||||
}
|
||||
}
|
||||
|
||||
b, _ := json.Marshal(idx)
|
||||
|
||||
return b
|
||||
}
|
||||
141
integration/client/lease_test.go
Normal file
141
integration/client/lease_test.go
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/leases"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
)
|
||||
|
||||
func TestLeaseResources(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
ctx, cancel := testContext(t)
|
||||
defer cancel()
|
||||
|
||||
client, err := newClient(t, address)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var (
|
||||
ls = client.LeasesService()
|
||||
cs = client.ContentStore()
|
||||
imgSrv = client.ImageService()
|
||||
sn = client.SnapshotService("native")
|
||||
)
|
||||
|
||||
l, err := ls.Create(ctx, leases.WithRandomID())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ls.Delete(ctx, l, leases.SynchronousDelete)
|
||||
|
||||
// step 1: download image
|
||||
imageName := "docker.io/library/busybox:1.25"
|
||||
|
||||
image, err := client.Pull(ctx, imageName, WithPullUnpack, WithPullSnapshotter("native"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer imgSrv.Delete(ctx, imageName)
|
||||
|
||||
// both the config and snapshotter should exist
|
||||
cfgDesc, err := image.Config(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := cs.Info(ctx, cfgDesc.Digest); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dgsts, err := image.RootFS(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chainID := identity.ChainID(dgsts)
|
||||
|
||||
if _, err := sn.Stat(ctx, chainID.String()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// step 2: reference snapshotter with lease
|
||||
r := leases.Resource{
|
||||
ID: chainID.String(),
|
||||
Type: "snapshots/native",
|
||||
}
|
||||
|
||||
if err := ls.AddResource(ctx, l, r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err := ls.ListResources(ctx, l)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 1 || list[0] != r {
|
||||
t.Fatalf("expected (%v), but got (%v)", []leases.Resource{r}, list)
|
||||
}
|
||||
|
||||
// step 3: remove image and check the status of snapshotter and content
|
||||
if err := imgSrv.Delete(ctx, imageName, images.SynchronousDelete()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// config should be removed but the snapshotter should exist
|
||||
if _, err := cs.Info(ctx, cfgDesc.Digest); !errdefs.IsNotFound(err) {
|
||||
t.Fatalf("expected error(%v), but got(%v)", errdefs.ErrNotFound, err)
|
||||
}
|
||||
|
||||
if _, err := sn.Stat(ctx, chainID.String()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// step 4: remove resource from the lease and check the list API
|
||||
if err := ls.DeleteResource(ctx, l, r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err = ls.ListResources(ctx, l)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(list) != 0 {
|
||||
t.Fatalf("expected nothing, but got (%v)", list)
|
||||
}
|
||||
|
||||
// step 5: remove the lease to check the status of snapshotter
|
||||
if err := ls.Delete(ctx, l, leases.SynchronousDelete); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := sn.Stat(ctx, chainID.String()); !errdefs.IsNotFound(err) {
|
||||
t.Fatalf("expected error(%v), but got(%v)", errdefs.ErrNotFound, err)
|
||||
}
|
||||
}
|
||||
116
integration/client/restart_monitor_linux_test.go
Normal file
116
integration/client/restart_monitor_linux_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/oci"
|
||||
)
|
||||
|
||||
// TestRestartMonitor tests restarting containers
|
||||
// with the restart monitor service plugin
|
||||
func TestRestartMonitor(t *testing.T) {
|
||||
const (
|
||||
interval = 10 * time.Second
|
||||
epsilon = 1 * time.Second
|
||||
)
|
||||
configTOML := fmt.Sprintf(`
|
||||
version = 2
|
||||
[plugins]
|
||||
[plugins."io.containerd.internal.v1.restart"]
|
||||
interval = "%s"
|
||||
`, interval.String())
|
||||
client, _, cleanup := newDaemonWithConfig(t, configTOML)
|
||||
defer cleanup()
|
||||
|
||||
var (
|
||||
ctx, cancel = testContext(t)
|
||||
id = t.Name()
|
||||
)
|
||||
defer cancel()
|
||||
|
||||
image, err := client.Pull(ctx, testImage, WithPullUnpack)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container, err := client.NewContainer(ctx, id,
|
||||
WithNewSnapshot(id, image),
|
||||
WithNewSpec(
|
||||
oci.WithImageConfig(image),
|
||||
withProcessArgs("sleep", "infinity"),
|
||||
),
|
||||
withRestartStatus(Running),
|
||||
)
|
||||
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, WithProcessKill)
|
||||
|
||||
if err := task.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
task.Kill(ctx, syscall.SIGKILL)
|
||||
begin := time.Now()
|
||||
deadline := begin.Add(interval).Add(epsilon)
|
||||
for time.Now().Before(deadline) {
|
||||
status, err := task.Status(ctx)
|
||||
now := time.Now()
|
||||
if err != nil {
|
||||
// ErrNotFound is expected here, because the restart monitor
|
||||
// temporarily removes the task before restarting.
|
||||
t.Logf("%v: err=%v", now, err)
|
||||
} else {
|
||||
t.Logf("%v: status=%q", now, status)
|
||||
|
||||
if status.Status == Running {
|
||||
elapsed := time.Since(begin)
|
||||
t.Logf("the task was restarted within %s", elapsed.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(epsilon)
|
||||
}
|
||||
t.Fatalf("the task was not restarted in %s + %s",
|
||||
interval.String(), epsilon.String())
|
||||
}
|
||||
|
||||
// withRestartStatus is a copy of "github.com/containerd/containerd/runtime/restart".WithStatus.
|
||||
// This copy is needed because `go test` refuses circular imports.
|
||||
func withRestartStatus(status ProcessStatus) func(context.Context, *Client, *containers.Container) error {
|
||||
return func(_ context.Context, _ *Client, c *containers.Container) error {
|
||||
if c.Labels == nil {
|
||||
c.Labels = make(map[string]string)
|
||||
}
|
||||
c.Labels["containerd.io/restart.status"] = string(status)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
48
integration/client/signals_test.go
Normal file
48
integration/client/signals_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
)
|
||||
|
||||
func TestParseSignal(t *testing.T) {
|
||||
testSignals := []struct {
|
||||
raw string
|
||||
want syscall.Signal
|
||||
err bool
|
||||
}{
|
||||
{"1", syscall.Signal(1), false},
|
||||
{"SIGKILL", syscall.SIGKILL, false},
|
||||
{"NONEXIST", 0, true},
|
||||
}
|
||||
for _, ts := range testSignals {
|
||||
t.Run(fmt.Sprintf("%s/%d/%t", ts.raw, ts.want, ts.err), func(t *testing.T) {
|
||||
got, err := ParseSignal(ts.raw)
|
||||
if ts.err && err == nil {
|
||||
t.Errorf("ParseSignal(%s) should return error", ts.raw)
|
||||
}
|
||||
if !ts.err && got != ts.want {
|
||||
t.Errorf("ParseSignal(%s) return %d, want %d", ts.raw, got, ts.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
51
integration/client/snapshot_test.go
Normal file
51
integration/client/snapshot_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/containerd/containerd/snapshots/testsuite"
|
||||
)
|
||||
|
||||
func newSnapshotter(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
|
||||
client, err := New(address)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sn := client.SnapshotService(DefaultSnapshotter)
|
||||
|
||||
return sn, func() error {
|
||||
// no need to close remote snapshotter
|
||||
return client.Close()
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestSnapshotterClient(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("snapshots not yet supported on Windows")
|
||||
}
|
||||
testsuite.SnapshotterSuite(t, "SnapshotterClient", newSnapshotter)
|
||||
}
|
||||
64
integration/client/task_opts_unix_test.go
Normal file
64
integration/client/task_opts_unix_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
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 containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/runtime/linux/runctypes"
|
||||
)
|
||||
|
||||
func TestWithNoNewKeyringAddsNoNewKeyringToOptions(t *testing.T) {
|
||||
var taskInfo TaskInfo
|
||||
var ctx context.Context
|
||||
var client Client
|
||||
|
||||
err := WithNoNewKeyring(ctx, &client, &taskInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opts := taskInfo.Options.(*runctypes.CreateOptions)
|
||||
|
||||
if !opts.NoNewKeyring {
|
||||
t.Fatal("NoNewKeyring set on WithNoNewKeyring")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWithNoNewKeyringDoesNotOverwriteOtherOptions(t *testing.T) {
|
||||
var taskInfo TaskInfo
|
||||
var ctx context.Context
|
||||
var client Client
|
||||
|
||||
taskInfo.Options = &runctypes.CreateOptions{NoPivotRoot: true}
|
||||
|
||||
err := WithNoNewKeyring(ctx, &client, &taskInfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opts := taskInfo.Options.(*runctypes.CreateOptions)
|
||||
|
||||
if !opts.NoPivotRoot {
|
||||
t.Fatal("WithNoNewKeyring overwrote other options")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user