github.com/containerd/Containerd@v1.4.13/client_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"os/exec"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/containerd/containerd/defaults"
    32  	"github.com/containerd/containerd/errdefs"
    33  	"github.com/containerd/containerd/images"
    34  	"github.com/containerd/containerd/leases"
    35  	"github.com/containerd/containerd/log"
    36  	"github.com/containerd/containerd/log/logtest"
    37  	"github.com/containerd/containerd/namespaces"
    38  	"github.com/containerd/containerd/pkg/testutil"
    39  	"github.com/containerd/containerd/platforms"
    40  	"github.com/containerd/containerd/sys"
    41  	"github.com/opencontainers/go-digest"
    42  	"github.com/opencontainers/image-spec/identity"
    43  	"github.com/sirupsen/logrus"
    44  )
    45  
    46  var (
    47  	address           string
    48  	noDaemon          bool
    49  	noCriu            bool
    50  	supportsCriu      bool
    51  	testNamespace     = "testing"
    52  	ctrdStdioFilePath string
    53  
    54  	ctrd = &daemon{}
    55  )
    56  
    57  func init() {
    58  	flag.StringVar(&address, "address", defaultAddress, "The address to the containerd socket for use in the tests")
    59  	flag.BoolVar(&noDaemon, "no-daemon", false, "Do not start a dedicated daemon for the tests")
    60  	flag.BoolVar(&noCriu, "no-criu", false, "Do not run the checkpoint tests")
    61  }
    62  
    63  func testContext(t testing.TB) (context.Context, context.CancelFunc) {
    64  	ctx, cancel := context.WithCancel(context.Background())
    65  	ctx = namespaces.WithNamespace(ctx, testNamespace)
    66  	if t != nil {
    67  		ctx = logtest.WithT(ctx, t)
    68  	}
    69  	return ctx, cancel
    70  }
    71  
    72  func TestMain(m *testing.M) {
    73  	flag.Parse()
    74  	if testing.Short() {
    75  		os.Exit(m.Run())
    76  	}
    77  	testutil.RequiresRootM()
    78  	// check if criu is installed on the system
    79  	_, err := exec.LookPath("criu")
    80  	supportsCriu = err == nil && !noCriu
    81  
    82  	var (
    83  		buf         = bytes.NewBuffer(nil)
    84  		ctx, cancel = testContext(nil)
    85  	)
    86  	defer cancel()
    87  
    88  	if !noDaemon {
    89  		sys.ForceRemoveAll(defaultRoot)
    90  
    91  		stdioFile, err := ioutil.TempFile("", "")
    92  		if err != nil {
    93  			fmt.Fprintf(os.Stderr, "could not create a new stdio temp file: %s\n", err)
    94  			os.Exit(1)
    95  		}
    96  		defer func() {
    97  			stdioFile.Close()
    98  			os.Remove(stdioFile.Name())
    99  		}()
   100  		ctrdStdioFilePath = stdioFile.Name()
   101  		stdioWriter := io.MultiWriter(stdioFile, buf)
   102  
   103  		err = ctrd.start("containerd", address, []string{
   104  			"--root", defaultRoot,
   105  			"--state", defaultState,
   106  			"--log-level", "debug",
   107  			"--config", createShimDebugConfig(),
   108  		}, stdioWriter, stdioWriter)
   109  		if err != nil {
   110  			fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
   111  			os.Exit(1)
   112  		}
   113  	}
   114  
   115  	waitCtx, waitCancel := context.WithTimeout(ctx, 4*time.Second)
   116  	client, err := ctrd.waitForStart(waitCtx)
   117  	waitCancel()
   118  	if err != nil {
   119  		ctrd.Kill()
   120  		ctrd.Wait()
   121  		fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
   122  		os.Exit(1)
   123  	}
   124  
   125  	// print out the version in information
   126  	version, err := client.Version(ctx)
   127  	if err != nil {
   128  		fmt.Fprintf(os.Stderr, "error getting version: %s\n", err)
   129  		os.Exit(1)
   130  	}
   131  
   132  	// allow comparison with containerd under test
   133  	log.G(ctx).WithFields(logrus.Fields{
   134  		"version":  version.Version,
   135  		"revision": version.Revision,
   136  		"runtime":  os.Getenv("TEST_RUNTIME"),
   137  	}).Info("running tests against containerd")
   138  
   139  	// pull a seed image
   140  	log.G(ctx).Info("start to pull seed image")
   141  	if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
   142  		fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
   143  		ctrd.Kill()
   144  		ctrd.Wait()
   145  		os.Exit(1)
   146  	}
   147  
   148  	if err := client.Close(); err != nil {
   149  		fmt.Fprintln(os.Stderr, "failed to close client", err)
   150  	}
   151  
   152  	// run the test
   153  	status := m.Run()
   154  
   155  	if !noDaemon {
   156  		// tear down the daemon and resources created
   157  		if err := ctrd.Stop(); err != nil {
   158  			if err := ctrd.Kill(); err != nil {
   159  				fmt.Fprintln(os.Stderr, "failed to signal containerd", err)
   160  			}
   161  		}
   162  		if err := ctrd.Wait(); err != nil {
   163  			if _, ok := err.(*exec.ExitError); !ok {
   164  				fmt.Fprintln(os.Stderr, "failed to wait for containerd", err)
   165  			}
   166  		}
   167  
   168  		if err := sys.ForceRemoveAll(defaultRoot); err != nil {
   169  			fmt.Fprintln(os.Stderr, "failed to remove test root dir", err)
   170  			os.Exit(1)
   171  		}
   172  
   173  		// only print containerd logs if the test failed or tests were run with -v
   174  		if status != 0 || testing.Verbose() {
   175  			fmt.Fprintln(os.Stderr, buf.String())
   176  		}
   177  	}
   178  	os.Exit(status)
   179  }
   180  
   181  func newClient(t testing.TB, address string, opts ...ClientOpt) (*Client, error) {
   182  	if testing.Short() {
   183  		t.Skip()
   184  	}
   185  	if rt := os.Getenv("TEST_RUNTIME"); rt != "" {
   186  		opts = append(opts, WithDefaultRuntime(rt))
   187  	}
   188  	// testutil.RequiresRoot(t) is not needed here (already called in TestMain)
   189  	return New(address, opts...)
   190  }
   191  
   192  func TestNewClient(t *testing.T) {
   193  	t.Parallel()
   194  
   195  	client, err := newClient(t, address)
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	if client == nil {
   200  		t.Fatal("New() returned nil client")
   201  	}
   202  	if err := client.Close(); err != nil {
   203  		t.Errorf("client closed returned error %v", err)
   204  	}
   205  }
   206  
   207  // All the container's tests depends on this, we need it to run first.
   208  func TestImagePull(t *testing.T) {
   209  	client, err := newClient(t, address)
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  	defer client.Close()
   214  
   215  	ctx, cancel := testContext(t)
   216  	defer cancel()
   217  	_, err = client.Pull(ctx, testImage, WithPlatformMatcher(platforms.Default()))
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  }
   222  
   223  func TestImagePullWithDiscardContent(t *testing.T) {
   224  	client, err := newClient(t, address)
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	defer client.Close()
   229  
   230  	ctx, cancel := testContext(t)
   231  	defer cancel()
   232  
   233  	err = client.ImageService().Delete(ctx, testImage, images.SynchronousDelete())
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  
   238  	ls := client.LeasesService()
   239  	l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(24*time.Hour))
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	ctx = leases.WithLease(ctx, l.ID)
   244  	img, err := client.Pull(ctx, testImage,
   245  		WithPlatformMatcher(platforms.Default()),
   246  		WithPullUnpack,
   247  		WithChildLabelMap(images.ChildGCLabelsFilterLayers),
   248  	)
   249  	// Synchronously garbage collect contents
   250  	if errL := ls.Delete(ctx, l, leases.SynchronousDelete); errL != nil {
   251  		t.Fatal(errL)
   252  	}
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  
   257  	// Check if all layer contents have been unpacked and aren't preserved
   258  	var (
   259  		diffIDs []digest.Digest
   260  		layers  []digest.Digest
   261  	)
   262  	cs := client.ContentStore()
   263  	manifest, err := images.Manifest(ctx, cs, img.Target(), platforms.Default())
   264  	if err != nil {
   265  		t.Fatal(err)
   266  	}
   267  	if len(manifest.Layers) == 0 {
   268  		t.Fatalf("failed to get children from %v", img.Target())
   269  	}
   270  	for _, l := range manifest.Layers {
   271  		layers = append(layers, l.Digest)
   272  	}
   273  	config, err := images.Config(ctx, cs, img.Target(), platforms.Default())
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	diffIDs, err = images.RootFS(ctx, cs, config)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	if len(layers) != len(diffIDs) {
   282  		t.Fatalf("number of layers and diffIDs don't match: %d != %d", len(layers), len(diffIDs))
   283  	} else if len(layers) == 0 {
   284  		t.Fatalf("there is no layers in the target image(parent: %v)", img.Target())
   285  	}
   286  	var (
   287  		sn    = client.SnapshotService("")
   288  		chain []digest.Digest
   289  	)
   290  	for i, dgst := range layers {
   291  		chain = append(chain, diffIDs[i])
   292  		chainID := identity.ChainID(chain).String()
   293  		if _, err := sn.Stat(ctx, chainID); err != nil {
   294  			t.Errorf("snapshot %v must exist: %v", chainID, err)
   295  		}
   296  		if _, err := cs.Info(ctx, dgst); err == nil || !errdefs.IsNotFound(err) {
   297  			t.Errorf("content %v must be garbage collected: %v", dgst, err)
   298  		}
   299  	}
   300  }
   301  
   302  func TestImagePullAllPlatforms(t *testing.T) {
   303  	client, err := newClient(t, address)
   304  	if err != nil {
   305  		t.Fatal(err)
   306  	}
   307  	defer client.Close()
   308  	ctx, cancel := testContext(t)
   309  	defer cancel()
   310  
   311  	cs := client.ContentStore()
   312  	img, err := client.Fetch(ctx, "ghcr.io/containerd/busybox:latest")
   313  	if err != nil {
   314  		t.Fatal(err)
   315  	}
   316  	index := img.Target
   317  	manifests, err := images.Children(ctx, cs, index)
   318  	if err != nil {
   319  		t.Fatal(err)
   320  	}
   321  	for _, manifest := range manifests {
   322  		children, err := images.Children(ctx, cs, manifest)
   323  		if err != nil {
   324  			t.Fatal("Th")
   325  		}
   326  		// check if childless data type has blob in content store
   327  		for _, desc := range children {
   328  			ra, err := cs.ReaderAt(ctx, desc)
   329  			if err != nil {
   330  				t.Fatal(err)
   331  			}
   332  			ra.Close()
   333  		}
   334  	}
   335  }
   336  
   337  func TestImagePullSomePlatforms(t *testing.T) {
   338  	client, err := newClient(t, address)
   339  	if err != nil {
   340  		t.Fatal(err)
   341  	}
   342  	defer client.Close()
   343  	ctx, cancel := testContext(t)
   344  	defer cancel()
   345  
   346  	cs := client.ContentStore()
   347  	platformList := []string{"linux/amd64", "linux/arm64/v8", "linux/s390x"}
   348  	m := make(map[string]platforms.Matcher)
   349  	var opts []RemoteOpt
   350  
   351  	for _, platform := range platformList {
   352  		p, err := platforms.Parse(platform)
   353  		if err != nil {
   354  			t.Fatal(err)
   355  		}
   356  		m[platform] = platforms.NewMatcher(p)
   357  		opts = append(opts, WithPlatform(platform))
   358  	}
   359  
   360  	img, err := client.Fetch(ctx, "k8s.gcr.io/pause:3.1", opts...)
   361  	if err != nil {
   362  		t.Fatal(err)
   363  	}
   364  
   365  	index := img.Target
   366  	manifests, err := images.Children(ctx, cs, index)
   367  	if err != nil {
   368  		t.Fatal(err)
   369  	}
   370  
   371  	count := 0
   372  	for _, manifest := range manifests {
   373  		children, err := images.Children(ctx, cs, manifest)
   374  
   375  		found := false
   376  		for _, matcher := range m {
   377  			if manifest.Platform == nil {
   378  				t.Fatal("manifest should have proper platform")
   379  			}
   380  			if matcher.Match(*manifest.Platform) {
   381  				count++
   382  				found = true
   383  			}
   384  		}
   385  
   386  		if found {
   387  			if len(children) == 0 {
   388  				t.Fatal("manifest should have pulled children content")
   389  			}
   390  
   391  			// check if childless data type has blob in content store
   392  			for _, desc := range children {
   393  				ra, err := cs.ReaderAt(ctx, desc)
   394  				if err != nil {
   395  					t.Fatal(err)
   396  				}
   397  				ra.Close()
   398  			}
   399  		} else if err == nil {
   400  			t.Fatal("manifest should not have pulled children content")
   401  		}
   402  	}
   403  
   404  	if count != len(platformList) {
   405  		t.Fatal("expected a different number of pulled manifests")
   406  	}
   407  }
   408  
   409  func TestImagePullSchema1(t *testing.T) {
   410  	client, err := newClient(t, address)
   411  	if err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	defer client.Close()
   415  
   416  	ctx, cancel := testContext(t)
   417  	defer cancel()
   418  	schema1TestImage := "gcr.io/google_containers/pause:3.0@sha256:0d093c962a6c2dd8bb8727b661e2b5f13e9df884af9945b4cc7088d9350cd3ee"
   419  	_, err = client.Pull(ctx, schema1TestImage, WithPlatform(platforms.DefaultString()), WithSchema1Conversion)
   420  	if err != nil {
   421  		t.Fatal(err)
   422  	}
   423  }
   424  
   425  func TestImagePullWithConcurrencyLimit(t *testing.T) {
   426  	client, err := newClient(t, address)
   427  	if err != nil {
   428  		t.Fatal(err)
   429  	}
   430  	defer client.Close()
   431  
   432  	ctx, cancel := testContext(t)
   433  	defer cancel()
   434  	_, err = client.Pull(ctx, testImage,
   435  		WithPlatformMatcher(platforms.Default()),
   436  		WithMaxConcurrentDownloads(2))
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  }
   441  
   442  func TestClientReconnect(t *testing.T) {
   443  	t.Parallel()
   444  
   445  	ctx, cancel := testContext(t)
   446  	defer cancel()
   447  
   448  	client, err := newClient(t, address)
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  	if client == nil {
   453  		t.Fatal("New() returned nil client")
   454  	}
   455  	ok, err := client.IsServing(ctx)
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  	if !ok {
   460  		t.Fatal("containerd is not serving")
   461  	}
   462  	if err := client.Reconnect(); err != nil {
   463  		t.Fatal(err)
   464  	}
   465  	if ok, err = client.IsServing(ctx); err != nil {
   466  		t.Fatal(err)
   467  	}
   468  	if !ok {
   469  		t.Fatal("containerd is not serving")
   470  	}
   471  	if err := client.Close(); err != nil {
   472  		t.Errorf("client closed returned error %v", err)
   473  	}
   474  }
   475  
   476  func createShimDebugConfig() string {
   477  	f, err := ioutil.TempFile("", "containerd-config-")
   478  	if err != nil {
   479  		fmt.Fprintf(os.Stderr, "Failed to create config file: %s\n", err)
   480  		os.Exit(1)
   481  	}
   482  	defer f.Close()
   483  	if _, err := f.WriteString("version = 1\n"); err != nil {
   484  		fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
   485  		os.Exit(1)
   486  	}
   487  
   488  	if _, err := f.WriteString("[plugins.linux]\n\tshim_debug = true\n"); err != nil {
   489  		fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
   490  		os.Exit(1)
   491  	}
   492  
   493  	return f.Name()
   494  }
   495  
   496  func TestDefaultRuntimeWithNamespaceLabels(t *testing.T) {
   497  	client, err := newClient(t, address)
   498  	if err != nil {
   499  		t.Fatal(err)
   500  	}
   501  	defer client.Close()
   502  
   503  	ctx, cancel := testContext(t)
   504  	defer cancel()
   505  	namespaces := client.NamespaceService()
   506  	testRuntime := "testRuntime"
   507  	runtimeLabel := defaults.DefaultRuntimeNSLabel
   508  	if err := namespaces.SetLabel(ctx, testNamespace, runtimeLabel, testRuntime); err != nil {
   509  		t.Fatal(err)
   510  	}
   511  
   512  	testClient, err := New(address, WithDefaultNamespace(testNamespace))
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  	defer testClient.Close()
   517  	if testClient.runtime != testRuntime {
   518  		t.Error("failed to set default runtime from namespace labels")
   519  	}
   520  }