github.com/demonoid81/containerd@v1.3.4/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/images"
    33  	"github.com/containerd/containerd/log"
    34  	"github.com/containerd/containerd/log/logtest"
    35  	"github.com/containerd/containerd/namespaces"
    36  	"github.com/containerd/containerd/pkg/testutil"
    37  	"github.com/containerd/containerd/platforms"
    38  	"github.com/containerd/containerd/sys"
    39  	"github.com/sirupsen/logrus"
    40  )
    41  
    42  var (
    43  	address           string
    44  	noDaemon          bool
    45  	noCriu            bool
    46  	supportsCriu      bool
    47  	testNamespace     = "testing"
    48  	ctrdStdioFilePath string
    49  
    50  	ctrd = &daemon{}
    51  )
    52  
    53  func init() {
    54  	flag.StringVar(&address, "address", defaultAddress, "The address to the containerd socket for use in the tests")
    55  	flag.BoolVar(&noDaemon, "no-daemon", false, "Do not start a dedicated daemon for the tests")
    56  	flag.BoolVar(&noCriu, "no-criu", false, "Do not run the checkpoint tests")
    57  }
    58  
    59  func testContext(t testing.TB) (context.Context, context.CancelFunc) {
    60  	ctx, cancel := context.WithCancel(context.Background())
    61  	ctx = namespaces.WithNamespace(ctx, testNamespace)
    62  	if t != nil {
    63  		ctx = logtest.WithT(ctx, t)
    64  	}
    65  	return ctx, cancel
    66  }
    67  
    68  func TestMain(m *testing.M) {
    69  	flag.Parse()
    70  	if testing.Short() {
    71  		os.Exit(m.Run())
    72  	}
    73  	testutil.RequiresRootM()
    74  	// check if criu is installed on the system
    75  	_, err := exec.LookPath("criu")
    76  	supportsCriu = err == nil && !noCriu
    77  
    78  	var (
    79  		buf         = bytes.NewBuffer(nil)
    80  		ctx, cancel = testContext(nil)
    81  	)
    82  	defer cancel()
    83  
    84  	if !noDaemon {
    85  		sys.ForceRemoveAll(defaultRoot)
    86  
    87  		stdioFile, err := ioutil.TempFile("", "")
    88  		if err != nil {
    89  			fmt.Fprintf(os.Stderr, "could not create a new stdio temp file: %s\n", err)
    90  			os.Exit(1)
    91  		}
    92  		defer func() {
    93  			stdioFile.Close()
    94  			os.Remove(stdioFile.Name())
    95  		}()
    96  		ctrdStdioFilePath = stdioFile.Name()
    97  		stdioWriter := io.MultiWriter(stdioFile, buf)
    98  
    99  		err = ctrd.start("containerd", address, []string{
   100  			"--root", defaultRoot,
   101  			"--state", defaultState,
   102  			"--log-level", "debug",
   103  			"--config", createShimDebugConfig(),
   104  		}, stdioWriter, stdioWriter)
   105  		if err != nil {
   106  			fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
   107  			os.Exit(1)
   108  		}
   109  	}
   110  
   111  	waitCtx, waitCancel := context.WithTimeout(ctx, 2*time.Second)
   112  	client, err := ctrd.waitForStart(waitCtx)
   113  	waitCancel()
   114  	if err != nil {
   115  		ctrd.Kill()
   116  		ctrd.Wait()
   117  		fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
   118  		os.Exit(1)
   119  	}
   120  
   121  	// print out the version in information
   122  	version, err := client.Version(ctx)
   123  	if err != nil {
   124  		fmt.Fprintf(os.Stderr, "error getting version: %s\n", err)
   125  		os.Exit(1)
   126  	}
   127  
   128  	// allow comparison with containerd under test
   129  	log.G(ctx).WithFields(logrus.Fields{
   130  		"version":  version.Version,
   131  		"revision": version.Revision,
   132  		"runtime":  os.Getenv("TEST_RUNTIME"),
   133  	}).Info("running tests against containerd")
   134  
   135  	// pull a seed image
   136  	log.G(ctx).Info("start to pull seed image")
   137  	if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
   138  		fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
   139  		ctrd.Kill()
   140  		ctrd.Wait()
   141  		os.Exit(1)
   142  	}
   143  
   144  	if err := client.Close(); err != nil {
   145  		fmt.Fprintln(os.Stderr, "failed to close client", err)
   146  	}
   147  
   148  	// run the test
   149  	status := m.Run()
   150  
   151  	if !noDaemon {
   152  		// tear down the daemon and resources created
   153  		if err := ctrd.Stop(); err != nil {
   154  			if err := ctrd.Kill(); err != nil {
   155  				fmt.Fprintln(os.Stderr, "failed to signal containerd", err)
   156  			}
   157  		}
   158  		if err := ctrd.Wait(); err != nil {
   159  			if _, ok := err.(*exec.ExitError); !ok {
   160  				fmt.Fprintln(os.Stderr, "failed to wait for containerd", err)
   161  			}
   162  		}
   163  
   164  		if err := sys.ForceRemoveAll(defaultRoot); err != nil {
   165  			fmt.Fprintln(os.Stderr, "failed to remove test root dir", err)
   166  			os.Exit(1)
   167  		}
   168  		// only print containerd logs if the test failed
   169  		if status != 0 {
   170  			fmt.Fprintln(os.Stderr, buf.String())
   171  		}
   172  	}
   173  	os.Exit(status)
   174  }
   175  
   176  func newClient(t testing.TB, address string, opts ...ClientOpt) (*Client, error) {
   177  	if testing.Short() {
   178  		t.Skip()
   179  	}
   180  	if rt := os.Getenv("TEST_RUNTIME"); rt != "" {
   181  		opts = append(opts, WithDefaultRuntime(rt))
   182  	}
   183  	// testutil.RequiresRoot(t) is not needed here (already called in TestMain)
   184  	return New(address, opts...)
   185  }
   186  
   187  func TestNewClient(t *testing.T) {
   188  	t.Parallel()
   189  
   190  	client, err := newClient(t, address)
   191  	if err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	if client == nil {
   195  		t.Fatal("New() returned nil client")
   196  	}
   197  	if err := client.Close(); err != nil {
   198  		t.Errorf("client closed returned error %v", err)
   199  	}
   200  }
   201  
   202  // All the container's tests depends on this, we need it to run first.
   203  func TestImagePull(t *testing.T) {
   204  	client, err := newClient(t, address)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  	defer client.Close()
   209  
   210  	ctx, cancel := testContext(t)
   211  	defer cancel()
   212  	_, err = client.Pull(ctx, testImage, WithPlatformMatcher(platforms.Default()))
   213  	if err != nil {
   214  		t.Fatal(err)
   215  	}
   216  }
   217  
   218  func TestImagePullAllPlatforms(t *testing.T) {
   219  	client, err := newClient(t, address)
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	defer client.Close()
   224  	ctx, cancel := testContext(t)
   225  	defer cancel()
   226  
   227  	cs := client.ContentStore()
   228  	img, err := client.Fetch(ctx, "docker.io/library/busybox:latest")
   229  	if err != nil {
   230  		t.Fatal(err)
   231  	}
   232  	index := img.Target
   233  	manifests, err := images.Children(ctx, cs, index)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	for _, manifest := range manifests {
   238  		children, err := images.Children(ctx, cs, manifest)
   239  		if err != nil {
   240  			t.Fatal("Th")
   241  		}
   242  		// check if childless data type has blob in content store
   243  		for _, desc := range children {
   244  			ra, err := cs.ReaderAt(ctx, desc)
   245  			if err != nil {
   246  				t.Fatal(err)
   247  			}
   248  			ra.Close()
   249  		}
   250  	}
   251  }
   252  
   253  func TestImagePullSomePlatforms(t *testing.T) {
   254  	client, err := newClient(t, address)
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  	defer client.Close()
   259  	ctx, cancel := testContext(t)
   260  	defer cancel()
   261  
   262  	cs := client.ContentStore()
   263  	platformList := []string{"linux/amd64", "linux/arm64/v8", "linux/s390x"}
   264  	m := make(map[string]platforms.Matcher)
   265  	var opts []RemoteOpt
   266  
   267  	for _, platform := range platformList {
   268  		p, err := platforms.Parse(platform)
   269  		if err != nil {
   270  			t.Fatal(err)
   271  		}
   272  		m[platform] = platforms.NewMatcher(p)
   273  		opts = append(opts, WithPlatform(platform))
   274  	}
   275  
   276  	img, err := client.Fetch(ctx, "k8s.gcr.io/pause:3.1", opts...)
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  
   281  	index := img.Target
   282  	manifests, err := images.Children(ctx, cs, index)
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  
   287  	count := 0
   288  	for _, manifest := range manifests {
   289  		children, err := images.Children(ctx, cs, manifest)
   290  
   291  		found := false
   292  		for _, matcher := range m {
   293  			if manifest.Platform == nil {
   294  				t.Fatal("manifest should have proper platform")
   295  			}
   296  			if matcher.Match(*manifest.Platform) {
   297  				count++
   298  				found = true
   299  			}
   300  		}
   301  
   302  		if found {
   303  			if len(children) == 0 {
   304  				t.Fatal("manifest should have pulled children content")
   305  			}
   306  
   307  			// check if childless data type has blob in content store
   308  			for _, desc := range children {
   309  				ra, err := cs.ReaderAt(ctx, desc)
   310  				if err != nil {
   311  					t.Fatal(err)
   312  				}
   313  				ra.Close()
   314  			}
   315  		} else if err == nil {
   316  			t.Fatal("manifest should not have pulled children content")
   317  		}
   318  	}
   319  
   320  	if count != len(platformList) {
   321  		t.Fatal("expected a different number of pulled manifests")
   322  	}
   323  }
   324  
   325  func TestImagePullSchema1(t *testing.T) {
   326  	client, err := newClient(t, address)
   327  	if err != nil {
   328  		t.Fatal(err)
   329  	}
   330  	defer client.Close()
   331  
   332  	ctx, cancel := testContext(t)
   333  	defer cancel()
   334  	schema1TestImage := "gcr.io/google_containers/pause:3.0@sha256:0d093c962a6c2dd8bb8727b661e2b5f13e9df884af9945b4cc7088d9350cd3ee"
   335  	_, err = client.Pull(ctx, schema1TestImage, WithPlatform(platforms.DefaultString()), WithSchema1Conversion)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  }
   340  
   341  func TestImagePullWithConcurrencyLimit(t *testing.T) {
   342  	client, err := newClient(t, address)
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	defer client.Close()
   347  
   348  	ctx, cancel := testContext(t)
   349  	defer cancel()
   350  	_, err = client.Pull(ctx, testImage,
   351  		WithPlatformMatcher(platforms.Default()),
   352  		WithMaxConcurrentDownloads(2))
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  }
   357  
   358  func TestClientReconnect(t *testing.T) {
   359  	t.Parallel()
   360  
   361  	ctx, cancel := testContext(t)
   362  	defer cancel()
   363  
   364  	client, err := newClient(t, address)
   365  	if err != nil {
   366  		t.Fatal(err)
   367  	}
   368  	if client == nil {
   369  		t.Fatal("New() returned nil client")
   370  	}
   371  	ok, err := client.IsServing(ctx)
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  	if !ok {
   376  		t.Fatal("containerd is not serving")
   377  	}
   378  	if err := client.Reconnect(); err != nil {
   379  		t.Fatal(err)
   380  	}
   381  	if ok, err = client.IsServing(ctx); err != nil {
   382  		t.Fatal(err)
   383  	}
   384  	if !ok {
   385  		t.Fatal("containerd is not serving")
   386  	}
   387  	if err := client.Close(); err != nil {
   388  		t.Errorf("client closed returned error %v", err)
   389  	}
   390  }
   391  
   392  func createShimDebugConfig() string {
   393  	f, err := ioutil.TempFile("", "containerd-config-")
   394  	if err != nil {
   395  		fmt.Fprintf(os.Stderr, "Failed to create config file: %s\n", err)
   396  		os.Exit(1)
   397  	}
   398  	defer f.Close()
   399  	if _, err := f.WriteString("version = 1\n"); err != nil {
   400  		fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
   401  		os.Exit(1)
   402  	}
   403  
   404  	if _, err := f.WriteString("[plugins.linux]\n\tshim_debug = true\n"); err != nil {
   405  		fmt.Fprintf(os.Stderr, "Failed to write to config file %s: %s\n", f.Name(), err)
   406  		os.Exit(1)
   407  	}
   408  
   409  	return f.Name()
   410  }
   411  
   412  func TestDefaultRuntimeWithNamespaceLabels(t *testing.T) {
   413  	client, err := newClient(t, address)
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  	defer client.Close()
   418  
   419  	ctx, cancel := testContext(t)
   420  	defer cancel()
   421  	namespaces := client.NamespaceService()
   422  	testRuntime := "testRuntime"
   423  	runtimeLabel := defaults.DefaultRuntimeNSLabel
   424  	if err := namespaces.SetLabel(ctx, testNamespace, runtimeLabel, testRuntime); err != nil {
   425  		t.Fatal(err)
   426  	}
   427  
   428  	testClient, err := New(address, WithDefaultNamespace(testNamespace))
   429  	if err != nil {
   430  		t.Fatal(err)
   431  	}
   432  	defer testClient.Close()
   433  	if testClient.runtime != testRuntime {
   434  		t.Error("failed to set default runtime from namespace labels")
   435  	}
   436  }