github.com/demonoid81/containerd@v1.3.4/content/testsuite/testsuite.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 testsuite
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"math/rand"
    26  	"os"
    27  	"runtime"
    28  	"sync/atomic"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/containerd/containerd/content"
    33  	"github.com/containerd/containerd/errdefs"
    34  	"github.com/containerd/containerd/log/logtest"
    35  	"github.com/containerd/containerd/pkg/testutil"
    36  	digest "github.com/opencontainers/go-digest"
    37  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    38  	"github.com/pkg/errors"
    39  	"gotest.tools/assert"
    40  )
    41  
    42  const (
    43  	emptyDigest = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
    44  )
    45  
    46  // StoreInitFn initializes content store with given root and returns a function for
    47  // destroying the content store
    48  type StoreInitFn func(ctx context.Context, root string) (context.Context, content.Store, func() error, error)
    49  
    50  // ContentSuite runs a test suite on the content store given a factory function.
    51  func ContentSuite(t *testing.T, name string, storeFn StoreInitFn) {
    52  	t.Run("Writer", makeTest(t, name, storeFn, checkContentStoreWriter))
    53  	t.Run("UpdateStatus", makeTest(t, name, storeFn, checkUpdateStatus))
    54  	t.Run("CommitExists", makeTest(t, name, storeFn, checkCommitExists))
    55  	t.Run("Resume", makeTest(t, name, storeFn, checkResumeWriter))
    56  	t.Run("ResumeTruncate", makeTest(t, name, storeFn, checkResume(resumeTruncate)))
    57  	t.Run("ResumeDiscard", makeTest(t, name, storeFn, checkResume(resumeDiscard)))
    58  	t.Run("ResumeCopy", makeTest(t, name, storeFn, checkResume(resumeCopy)))
    59  	t.Run("ResumeCopySeeker", makeTest(t, name, storeFn, checkResume(resumeCopySeeker)))
    60  	t.Run("ResumeCopyReaderAt", makeTest(t, name, storeFn, checkResume(resumeCopyReaderAt)))
    61  	t.Run("SmallBlob", makeTest(t, name, storeFn, checkSmallBlob))
    62  	t.Run("Labels", makeTest(t, name, storeFn, checkLabels))
    63  
    64  	t.Run("CommitErrorState", makeTest(t, name, storeFn, checkCommitErrorState))
    65  }
    66  
    67  // ContentCrossNSSharedSuite runs a test suite under shared content policy
    68  func ContentCrossNSSharedSuite(t *testing.T, name string, storeFn StoreInitFn) {
    69  	t.Run("CrossNamespaceAppend", makeTest(t, name, storeFn, checkCrossNSAppend))
    70  	t.Run("CrossNamespaceShare", makeTest(t, name, storeFn, checkCrossNSShare))
    71  }
    72  
    73  // ContentCrossNSIsolatedSuite runs a test suite under isolated content policy
    74  func ContentCrossNSIsolatedSuite(t *testing.T, name string, storeFn StoreInitFn) {
    75  	t.Run("CrossNamespaceIsolate", makeTest(t, name, storeFn, checkCrossNSIsolate))
    76  }
    77  
    78  // ContextWrapper is used to decorate new context used inside the test
    79  // before using the context on the content store.
    80  // This can be used to support leasing and multiple namespaces tests.
    81  type ContextWrapper func(ctx context.Context) (context.Context, func(context.Context) error, error)
    82  
    83  type wrapperKey struct{}
    84  
    85  // SetContextWrapper sets the wrapper on the context for deriving
    86  // new test contexts from the context.
    87  func SetContextWrapper(ctx context.Context, w ContextWrapper) context.Context {
    88  	return context.WithValue(ctx, wrapperKey{}, w)
    89  }
    90  
    91  type nameKey struct{}
    92  
    93  // Name gets the test name from the context
    94  func Name(ctx context.Context) string {
    95  	name, ok := ctx.Value(nameKey{}).(string)
    96  	if !ok {
    97  		return ""
    98  	}
    99  	return name
   100  }
   101  
   102  func makeTest(t *testing.T, name string, storeFn func(ctx context.Context, root string) (context.Context, content.Store, func() error, error), fn func(ctx context.Context, t *testing.T, cs content.Store)) func(t *testing.T) {
   103  	return func(t *testing.T) {
   104  		ctx := context.WithValue(context.Background(), nameKey{}, name)
   105  		ctx = logtest.WithT(ctx, t)
   106  
   107  		tmpDir, err := ioutil.TempDir("", "content-suite-"+name+"-")
   108  		if err != nil {
   109  			t.Fatal(err)
   110  		}
   111  		defer os.RemoveAll(tmpDir)
   112  
   113  		ctx, cs, cleanup, err := storeFn(ctx, tmpDir)
   114  		if err != nil {
   115  			t.Fatal(err)
   116  		}
   117  		defer func() {
   118  			if err := cleanup(); err != nil && !t.Failed() {
   119  				t.Fatalf("Cleanup failed: %+v", err)
   120  			}
   121  		}()
   122  
   123  		w, ok := ctx.Value(wrapperKey{}).(ContextWrapper)
   124  		if ok {
   125  			var done func(context.Context) error
   126  			ctx, done, err = w(ctx)
   127  			if err != nil {
   128  				t.Fatalf("Error wrapping context: %+v", err)
   129  			}
   130  			defer func() {
   131  				if err := done(ctx); err != nil && !t.Failed() {
   132  					t.Fatalf("Wrapper release failed: %+v", err)
   133  				}
   134  			}()
   135  		}
   136  
   137  		defer testutil.DumpDirOnFailure(t, tmpDir)
   138  		fn(ctx, t, cs)
   139  	}
   140  }
   141  
   142  var labels = map[string]string{
   143  	"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
   144  }
   145  
   146  func checkContentStoreWriter(ctx context.Context, t *testing.T, cs content.Store) {
   147  	c1, d1 := createContent(256)
   148  	w1, err := cs.Writer(ctx, content.WithRef("c1"))
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  	defer w1.Close()
   153  
   154  	c2, d2 := createContent(256)
   155  	w2, err := cs.Writer(ctx, content.WithRef("c2"), content.WithDescriptor(ocispec.Descriptor{Size: int64(len(c2))}))
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	defer w2.Close()
   160  
   161  	c3, d3 := createContent(256)
   162  	w3, err := cs.Writer(ctx, content.WithRef("c3"), content.WithDescriptor(ocispec.Descriptor{Digest: d3}))
   163  	if err != nil {
   164  		t.Fatal(err)
   165  	}
   166  	defer w3.Close()
   167  
   168  	c4, d4 := createContent(256)
   169  	w4, err := cs.Writer(ctx, content.WithRef("c4"), content.WithDescriptor(ocispec.Descriptor{Size: int64(len(c4)), Digest: d4}))
   170  	if err != nil {
   171  		t.Fatal(err)
   172  	}
   173  	defer w4.Close()
   174  
   175  	smallbuf := make([]byte, 32)
   176  	for _, s := range []struct {
   177  		content []byte
   178  		digest  digest.Digest
   179  		writer  content.Writer
   180  	}{
   181  		{
   182  			content: c1,
   183  			digest:  d1,
   184  			writer:  w1,
   185  		},
   186  		{
   187  			content: c2,
   188  			digest:  d2,
   189  			writer:  w2,
   190  		},
   191  		{
   192  			content: c3,
   193  			digest:  d3,
   194  			writer:  w3,
   195  		},
   196  		{
   197  			content: c4,
   198  			digest:  d4,
   199  			writer:  w4,
   200  		},
   201  	} {
   202  		n, err := io.CopyBuffer(s.writer, bytes.NewReader(s.content), smallbuf)
   203  		if err != nil {
   204  			t.Fatal(err)
   205  		}
   206  
   207  		if n != int64(len(s.content)) {
   208  			t.Fatalf("Unexpected copy length %d, expected %d", n, len(s.content))
   209  		}
   210  
   211  		preCommit := time.Now()
   212  		if err := s.writer.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
   213  			t.Fatal(err)
   214  		}
   215  		postCommit := time.Now()
   216  
   217  		if s.writer.Digest() != s.digest {
   218  			t.Fatalf("Unexpected commit digest %s, expected %s", s.writer.Digest(), s.digest)
   219  		}
   220  
   221  		info := content.Info{
   222  			Digest: s.digest,
   223  			Size:   int64(len(s.content)),
   224  			Labels: labels,
   225  		}
   226  		if err := checkInfo(ctx, cs, s.digest, info, preCommit, postCommit, preCommit, postCommit); err != nil {
   227  			t.Fatalf("Check info failed: %+v", err)
   228  		}
   229  	}
   230  }
   231  
   232  func checkResumeWriter(ctx context.Context, t *testing.T, cs content.Store) {
   233  	checkWrite := func(t *testing.T, w io.Writer, p []byte) {
   234  		t.Helper()
   235  		n, err := w.Write(p)
   236  		if err != nil {
   237  			t.Fatal(err)
   238  		}
   239  
   240  		if n != len(p) {
   241  			t.Fatal("short write to content store")
   242  		}
   243  	}
   244  
   245  	var (
   246  		ref           = "cb"
   247  		cb, dgst      = createContent(256)
   248  		first, second = cb[:128], cb[128:]
   249  	)
   250  
   251  	preStart := time.Now()
   252  	w1, err := cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: dgst}))
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	}
   256  	postStart := time.Now()
   257  	preUpdate := postStart
   258  
   259  	checkWrite(t, w1, first)
   260  	postUpdate := time.Now()
   261  
   262  	dgstFirst := digest.FromBytes(first)
   263  	expected := content.Status{
   264  		Ref:      ref,
   265  		Offset:   int64(len(first)),
   266  		Total:    int64(len(cb)),
   267  		Expected: dgstFirst,
   268  	}
   269  
   270  	checkStatus(t, w1, expected, dgstFirst, preStart, postStart, preUpdate, postUpdate)
   271  	assert.NilError(t, w1.Close(), "close first writer")
   272  
   273  	w2, err := cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: dgst}))
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  
   278  	// status should be consistent with version before close.
   279  	checkStatus(t, w2, expected, dgstFirst, preStart, postStart, preUpdate, postUpdate)
   280  
   281  	preUpdate = time.Now()
   282  	checkWrite(t, w2, second)
   283  	postUpdate = time.Now()
   284  
   285  	expected.Offset = expected.Total
   286  	expected.Expected = dgst
   287  	checkStatus(t, w2, expected, dgst, preStart, postStart, preUpdate, postUpdate)
   288  
   289  	preCommit := time.Now()
   290  	if err := w2.Commit(ctx, 0, ""); err != nil {
   291  		t.Fatalf("commit failed: %+v", err)
   292  	}
   293  	postCommit := time.Now()
   294  
   295  	assert.NilError(t, w2.Close(), "close second writer")
   296  	info := content.Info{
   297  		Digest: dgst,
   298  		Size:   256,
   299  	}
   300  
   301  	if err := checkInfo(ctx, cs, dgst, info, preCommit, postCommit, preCommit, postCommit); err != nil {
   302  		t.Fatalf("Check info failed: %+v", err)
   303  	}
   304  }
   305  
   306  func checkCommitExists(ctx context.Context, t *testing.T, cs content.Store) {
   307  	c1, d1 := createContent(256)
   308  	if err := content.WriteBlob(ctx, cs, "c1", bytes.NewReader(c1), ocispec.Descriptor{Digest: d1}); err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	for i, tc := range []struct {
   313  		expected digest.Digest
   314  	}{
   315  		{
   316  			expected: d1,
   317  		},
   318  		{},
   319  	} {
   320  		w, err := cs.Writer(ctx, content.WithRef(fmt.Sprintf("c1-commitexists-%d", i)))
   321  		if err != nil {
   322  			t.Fatal(err)
   323  		}
   324  		if _, err := w.Write(c1); err != nil {
   325  			w.Close()
   326  			t.Fatal(err)
   327  		}
   328  		err = w.Commit(ctx, int64(len(c1)), tc.expected)
   329  		w.Close()
   330  		if err == nil {
   331  			t.Errorf("(%d) Expected already exists error", i)
   332  		} else if !errdefs.IsAlreadyExists(err) {
   333  			t.Fatalf("(%d) Unexpected error: %+v", i, err)
   334  		}
   335  	}
   336  }
   337  
   338  func checkRefNotAvailable(ctx context.Context, t *testing.T, cs content.Store, ref string) {
   339  	t.Helper()
   340  
   341  	w, err := cs.Writer(ctx, content.WithRef(ref))
   342  	if err == nil {
   343  		defer w.Close()
   344  		t.Fatal("writer created with ref, expected to be in use")
   345  	}
   346  	if !errdefs.IsUnavailable(err) {
   347  		t.Fatalf("Expected unavailable error, got %+v", err)
   348  	}
   349  }
   350  
   351  func checkCommitErrorState(ctx context.Context, t *testing.T, cs content.Store) {
   352  	c1, d1 := createContent(256)
   353  	_, d2 := createContent(256)
   354  	if err := content.WriteBlob(ctx, cs, "c1", bytes.NewReader(c1), ocispec.Descriptor{Digest: d1}); err != nil {
   355  		t.Fatal(err)
   356  	}
   357  
   358  	ref := "c1-commiterror-state"
   359  	w, err := cs.Writer(ctx, content.WithRef(ref))
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	if _, err := w.Write(c1); err != nil {
   364  		if err := w.Close(); err != nil {
   365  			t.Errorf("Close error: %+v", err)
   366  		}
   367  		t.Fatal(err)
   368  	}
   369  
   370  	checkRefNotAvailable(ctx, t, cs, ref)
   371  
   372  	// Check exists
   373  	err = w.Commit(ctx, int64(len(c1)), d1)
   374  	if err == nil {
   375  		t.Fatalf("Expected already exists error")
   376  	} else if !errdefs.IsAlreadyExists(err) {
   377  		if err := w.Close(); err != nil {
   378  			t.Errorf("Close error: %+v", err)
   379  		}
   380  		t.Fatalf("Unexpected error: %+v", err)
   381  	}
   382  
   383  	w, err = cs.Writer(ctx, content.WithRef(ref))
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  
   388  	checkRefNotAvailable(ctx, t, cs, ref)
   389  
   390  	if _, err := w.Write(c1); err != nil {
   391  		if err := w.Close(); err != nil {
   392  			t.Errorf("close error: %+v", err)
   393  		}
   394  		t.Fatal(err)
   395  	}
   396  
   397  	// Check exists without providing digest
   398  	err = w.Commit(ctx, int64(len(c1)), "")
   399  	if err == nil {
   400  		t.Fatalf("Expected already exists error")
   401  	} else if !errdefs.IsAlreadyExists(err) {
   402  		if err := w.Close(); err != nil {
   403  			t.Errorf("Close error: %+v", err)
   404  		}
   405  		t.Fatalf("Unexpected error: %+v", err)
   406  	}
   407  	w.Close()
   408  
   409  	w, err = cs.Writer(ctx, content.WithRef(ref))
   410  	if err != nil {
   411  		t.Fatal(err)
   412  	}
   413  
   414  	checkRefNotAvailable(ctx, t, cs, ref)
   415  
   416  	if _, err := w.Write(append(c1, []byte("more")...)); err != nil {
   417  		if err := w.Close(); err != nil {
   418  			t.Errorf("close error: %+v", err)
   419  		}
   420  		t.Fatal(err)
   421  	}
   422  
   423  	// Commit with the wrong digest should produce an error
   424  	err = w.Commit(ctx, int64(len(c1))+4, d2)
   425  	if err == nil {
   426  		t.Fatalf("Expected error from wrong digest")
   427  	} else if !errdefs.IsFailedPrecondition(err) {
   428  		t.Errorf("Unexpected error: %+v", err)
   429  	}
   430  
   431  	w.Close()
   432  	w, err = cs.Writer(ctx, content.WithRef(ref))
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  
   437  	checkRefNotAvailable(ctx, t, cs, ref)
   438  
   439  	// Commit with wrong size should also produce an error
   440  	err = w.Commit(ctx, int64(len(c1)), "")
   441  	if err == nil {
   442  		t.Fatalf("Expected error from wrong size")
   443  	} else if !errdefs.IsFailedPrecondition(err) {
   444  		t.Errorf("Unexpected error: %+v", err)
   445  	}
   446  
   447  	w.Close()
   448  	w, err = cs.Writer(ctx, content.WithRef(ref))
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  
   453  	checkRefNotAvailable(ctx, t, cs, ref)
   454  
   455  	// Now expect commit to succeed
   456  	if err := w.Commit(ctx, int64(len(c1))+4, ""); err != nil {
   457  		if err := w.Close(); err != nil {
   458  			t.Errorf("close error: %+v", err)
   459  		}
   460  		t.Fatalf("Failed to commit: %+v", err)
   461  	}
   462  
   463  	w.Close()
   464  	// Create another writer with same reference
   465  	w, err = cs.Writer(ctx, content.WithRef(ref))
   466  	if err != nil {
   467  		t.Fatalf("Failed to open writer: %+v", err)
   468  	}
   469  
   470  	if _, err := w.Write(c1); err != nil {
   471  		if err := w.Close(); err != nil {
   472  			t.Errorf("close error: %+v", err)
   473  		}
   474  		t.Fatal(err)
   475  	}
   476  
   477  	checkRefNotAvailable(ctx, t, cs, ref)
   478  
   479  	// Commit should fail due to already exists
   480  	err = w.Commit(ctx, int64(len(c1)), d1)
   481  	if err == nil {
   482  		t.Fatalf("Expected already exists error")
   483  	} else if !errdefs.IsAlreadyExists(err) {
   484  		if err := w.Close(); err != nil {
   485  			t.Errorf("close error: %+v", err)
   486  		}
   487  		t.Fatalf("Unexpected error: %+v", err)
   488  	}
   489  
   490  	w.Close()
   491  	w, err = cs.Writer(ctx, content.WithRef(ref))
   492  	if err != nil {
   493  		t.Fatal(err)
   494  	}
   495  
   496  	checkRefNotAvailable(ctx, t, cs, ref)
   497  
   498  	if err := w.Close(); err != nil {
   499  		t.Fatalf("Close failed: %+v", err)
   500  	}
   501  
   502  	// Create another writer with same reference to check available
   503  	w, err = cs.Writer(ctx, content.WithRef(ref))
   504  	if err != nil {
   505  		t.Fatalf("Failed to open writer: %+v", err)
   506  	}
   507  	if err := w.Close(); err != nil {
   508  		t.Fatalf("Close failed: %+v", err)
   509  	}
   510  }
   511  
   512  func checkUpdateStatus(ctx context.Context, t *testing.T, cs content.Store) {
   513  	c1, d1 := createContent(256)
   514  
   515  	preStart := time.Now()
   516  	w1, err := cs.Writer(ctx, content.WithRef("c1"), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: d1}))
   517  	if err != nil {
   518  		t.Fatal(err)
   519  	}
   520  	defer w1.Close()
   521  	postStart := time.Now()
   522  
   523  	d := digest.FromBytes([]byte{})
   524  
   525  	expected := content.Status{
   526  		Ref:      "c1",
   527  		Total:    256,
   528  		Expected: d1,
   529  	}
   530  	preUpdate := preStart
   531  	postUpdate := postStart
   532  
   533  	checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate)
   534  
   535  	// Write first 64 bytes
   536  	preUpdate = time.Now()
   537  	if _, err := w1.Write(c1[:64]); err != nil {
   538  		t.Fatalf("Failed to write: %+v", err)
   539  	}
   540  	postUpdate = time.Now()
   541  	expected.Offset = 64
   542  	d = digest.FromBytes(c1[:64])
   543  	checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate)
   544  
   545  	// Write next 128 bytes
   546  	preUpdate = time.Now()
   547  	if _, err := w1.Write(c1[64:192]); err != nil {
   548  		t.Fatalf("Failed to write: %+v", err)
   549  	}
   550  	postUpdate = time.Now()
   551  	expected.Offset = 192
   552  	d = digest.FromBytes(c1[:192])
   553  	checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate)
   554  
   555  	// Write last 64 bytes
   556  	preUpdate = time.Now()
   557  	if _, err := w1.Write(c1[192:]); err != nil {
   558  		t.Fatalf("Failed to write: %+v", err)
   559  	}
   560  	postUpdate = time.Now()
   561  	expected.Offset = 256
   562  	checkStatus(t, w1, expected, d1, preStart, postStart, preUpdate, postUpdate)
   563  
   564  	preCommit := time.Now()
   565  	if err := w1.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
   566  		t.Fatalf("Commit failed: %+v", err)
   567  	}
   568  	postCommit := time.Now()
   569  
   570  	info := content.Info{
   571  		Digest: d1,
   572  		Size:   256,
   573  		Labels: labels,
   574  	}
   575  
   576  	if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preCommit, postCommit); err != nil {
   577  		t.Fatalf("Check info failed: %+v", err)
   578  	}
   579  }
   580  
   581  func checkLabels(ctx context.Context, t *testing.T, cs content.Store) {
   582  	c1, d1 := createContent(256)
   583  
   584  	w1, err := cs.Writer(ctx, content.WithRef("c1-checklabels"), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: d1}))
   585  	if err != nil {
   586  		t.Fatal(err)
   587  	}
   588  	defer w1.Close()
   589  
   590  	if _, err := w1.Write(c1); err != nil {
   591  		t.Fatalf("Failed to write: %+v", err)
   592  	}
   593  
   594  	rootTime := time.Now().UTC().Format(time.RFC3339)
   595  	labels := map[string]string{
   596  		"k1": "v1",
   597  		"k2": "v2",
   598  
   599  		"containerd.io/gc.root": rootTime,
   600  	}
   601  
   602  	preCommit := time.Now()
   603  	if err := w1.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil {
   604  		t.Fatalf("Commit failed: %+v", err)
   605  	}
   606  	postCommit := time.Now()
   607  
   608  	info := content.Info{
   609  		Digest: d1,
   610  		Size:   256,
   611  		Labels: labels,
   612  	}
   613  
   614  	if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preCommit, postCommit); err != nil {
   615  		t.Fatalf("Check info failed: %+v", err)
   616  	}
   617  
   618  	labels["k1"] = "newvalue"
   619  	delete(labels, "k2")
   620  	labels["k3"] = "v3"
   621  
   622  	info.Labels = labels
   623  	preUpdate := time.Now()
   624  	if _, err := cs.Update(ctx, info); err != nil {
   625  		t.Fatalf("Update failed: %+v", err)
   626  	}
   627  	postUpdate := time.Now()
   628  
   629  	if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preUpdate, postUpdate); err != nil {
   630  		t.Fatalf("Check info failed: %+v", err)
   631  	}
   632  
   633  	info.Labels = map[string]string{
   634  		"k1": "v1",
   635  
   636  		"containerd.io/gc.root": rootTime,
   637  	}
   638  	preUpdate = time.Now()
   639  	if _, err := cs.Update(ctx, info, "labels.k3", "labels.k1"); err != nil {
   640  		t.Fatalf("Update failed: %+v", err)
   641  	}
   642  	postUpdate = time.Now()
   643  
   644  	if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preUpdate, postUpdate); err != nil {
   645  		t.Fatalf("Check info failed: %+v", err)
   646  	}
   647  
   648  }
   649  
   650  func checkResume(rf func(context.Context, content.Writer, []byte, int64, int64, digest.Digest) error) func(ctx context.Context, t *testing.T, cs content.Store) {
   651  	return func(ctx context.Context, t *testing.T, cs content.Store) {
   652  		sizes := []int64{500, 5000, 50000}
   653  		truncations := []float64{0.0, 0.1, 0.5, 0.9, 1.0}
   654  
   655  		for i, size := range sizes {
   656  			for j, tp := range truncations {
   657  				b, d := createContent(size)
   658  				limit := int64(float64(size) * tp)
   659  				ref := fmt.Sprintf("ref-%d-%d", i, j)
   660  
   661  				w, err := cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d}))
   662  				if err != nil {
   663  					t.Fatal(err)
   664  				}
   665  
   666  				if _, err := w.Write(b[:limit]); err != nil {
   667  					w.Close()
   668  					t.Fatal(err)
   669  				}
   670  
   671  				if err := w.Close(); err != nil {
   672  					t.Fatal(err)
   673  				}
   674  
   675  				w, err = cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d}))
   676  				if err != nil {
   677  					t.Fatal(err)
   678  				}
   679  
   680  				st, err := w.Status()
   681  				if err != nil {
   682  					w.Close()
   683  					t.Fatal(err)
   684  				}
   685  
   686  				if st.Offset != limit {
   687  					w.Close()
   688  					t.Fatalf("Unexpected offset %d, expected %d", st.Offset, limit)
   689  				}
   690  
   691  				preCommit := time.Now()
   692  				if err := rf(ctx, w, b, limit, size, d); err != nil {
   693  					t.Fatalf("Resume failed: %+v", err)
   694  				}
   695  				postCommit := time.Now()
   696  
   697  				if err := w.Close(); err != nil {
   698  					t.Fatal(err)
   699  				}
   700  
   701  				info := content.Info{
   702  					Digest: d,
   703  					Size:   size,
   704  				}
   705  
   706  				if err := checkInfo(ctx, cs, d, info, preCommit, postCommit, preCommit, postCommit); err != nil {
   707  					t.Fatalf("Check info failed: %+v", err)
   708  				}
   709  			}
   710  		}
   711  	}
   712  }
   713  
   714  func resumeTruncate(ctx context.Context, w content.Writer, b []byte, written, size int64, dgst digest.Digest) error {
   715  	if err := w.Truncate(0); err != nil {
   716  		return errors.Wrap(err, "truncate failed")
   717  	}
   718  
   719  	if _, err := io.CopyBuffer(w, bytes.NewReader(b), make([]byte, 1024)); err != nil {
   720  		return errors.Wrap(err, "write failed")
   721  	}
   722  
   723  	return errors.Wrap(w.Commit(ctx, size, dgst), "commit failed")
   724  }
   725  
   726  func resumeDiscard(ctx context.Context, w content.Writer, b []byte, written, size int64, dgst digest.Digest) error {
   727  	if _, err := io.CopyBuffer(w, bytes.NewReader(b[written:]), make([]byte, 1024)); err != nil {
   728  		return errors.Wrap(err, "write failed")
   729  	}
   730  	return errors.Wrap(w.Commit(ctx, size, dgst), "commit failed")
   731  }
   732  
   733  func resumeCopy(ctx context.Context, w content.Writer, b []byte, _, size int64, dgst digest.Digest) error {
   734  	r := struct {
   735  		io.Reader
   736  	}{bytes.NewReader(b)}
   737  	return errors.Wrap(content.Copy(ctx, w, r, size, dgst), "copy failed")
   738  }
   739  
   740  func resumeCopySeeker(ctx context.Context, w content.Writer, b []byte, _, size int64, dgst digest.Digest) error {
   741  	r := struct {
   742  		io.ReadSeeker
   743  	}{bytes.NewReader(b)}
   744  	return errors.Wrap(content.Copy(ctx, w, r, size, dgst), "copy failed")
   745  }
   746  
   747  func resumeCopyReaderAt(ctx context.Context, w content.Writer, b []byte, _, size int64, dgst digest.Digest) error {
   748  	type readerAt interface {
   749  		io.Reader
   750  		io.ReaderAt
   751  	}
   752  	r := struct {
   753  		readerAt
   754  	}{bytes.NewReader(b)}
   755  	return errors.Wrap(content.Copy(ctx, w, r, size, dgst), "copy failed")
   756  }
   757  
   758  // checkSmallBlob tests reading a blob which is smaller than the read size.
   759  func checkSmallBlob(ctx context.Context, t *testing.T, store content.Store) {
   760  	blob := []byte(`foobar`)
   761  	blobSize := int64(len(blob))
   762  	blobDigest := digest.FromBytes(blob)
   763  	// test write
   764  	w, err := store.Writer(ctx, content.WithRef(t.Name()), content.WithDescriptor(ocispec.Descriptor{Size: blobSize, Digest: blobDigest}))
   765  	if err != nil {
   766  		t.Fatal(err)
   767  	}
   768  	if _, err := w.Write(blob); err != nil {
   769  		t.Fatal(err)
   770  	}
   771  	if err := w.Commit(ctx, blobSize, blobDigest); err != nil {
   772  		t.Fatal(err)
   773  	}
   774  	if err := w.Close(); err != nil {
   775  		t.Fatal(err)
   776  	}
   777  	// test read.
   778  	readSize := blobSize + 1
   779  	ra, err := store.ReaderAt(ctx, ocispec.Descriptor{Digest: blobDigest})
   780  	if err != nil {
   781  		t.Fatal(err)
   782  	}
   783  	r := io.NewSectionReader(ra, 0, readSize)
   784  	b, err := ioutil.ReadAll(r)
   785  	if err != nil {
   786  		t.Fatal(err)
   787  	}
   788  	if err := ra.Close(); err != nil {
   789  		t.Fatal(err)
   790  	}
   791  	d := digest.FromBytes(b)
   792  	if blobDigest != d {
   793  		t.Fatalf("expected %s (%q), got %s (%q)", blobDigest, string(blob),
   794  			d, string(b))
   795  	}
   796  }
   797  
   798  func checkCrossNSShare(ctx context.Context, t *testing.T, cs content.Store) {
   799  	wrap, ok := ctx.Value(wrapperKey{}).(ContextWrapper)
   800  	if !ok {
   801  		t.Skip("multiple contexts not supported")
   802  	}
   803  
   804  	var size int64 = 1000
   805  	b, d := createContent(size)
   806  	ref := fmt.Sprintf("ref-%d", size)
   807  	t1 := time.Now()
   808  
   809  	if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(b), ocispec.Descriptor{Size: size, Digest: d}); err != nil {
   810  		t.Fatal(err)
   811  	}
   812  
   813  	ctx2, done, err := wrap(context.Background())
   814  	if err != nil {
   815  		t.Fatal(err)
   816  	}
   817  	defer done(ctx2)
   818  
   819  	w, err := cs.Writer(ctx2, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d}))
   820  	if err != nil {
   821  		t.Fatal(err)
   822  	}
   823  	t2 := time.Now()
   824  
   825  	checkStatus(t, w, content.Status{
   826  		Ref:    ref,
   827  		Offset: size,
   828  		Total:  size,
   829  	}, d, t1, t2, t1, t2)
   830  
   831  	if err := w.Commit(ctx2, size, d); err != nil {
   832  		t.Fatal(err)
   833  	}
   834  	t3 := time.Now()
   835  
   836  	info := content.Info{
   837  		Digest: d,
   838  		Size:   size,
   839  	}
   840  	if err := checkContent(ctx, cs, d, info, t1, t3, t1, t3); err != nil {
   841  		t.Fatal(err)
   842  	}
   843  
   844  	if err := checkContent(ctx2, cs, d, info, t1, t3, t1, t3); err != nil {
   845  		t.Fatal(err)
   846  	}
   847  }
   848  
   849  func checkCrossNSAppend(ctx context.Context, t *testing.T, cs content.Store) {
   850  	wrap, ok := ctx.Value(wrapperKey{}).(ContextWrapper)
   851  	if !ok {
   852  		t.Skip("multiple contexts not supported")
   853  	}
   854  
   855  	var size int64 = 1000
   856  	b, d := createContent(size)
   857  	ref := fmt.Sprintf("ref-%d", size)
   858  	t1 := time.Now()
   859  
   860  	if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(b), ocispec.Descriptor{Size: size, Digest: d}); err != nil {
   861  		t.Fatal(err)
   862  	}
   863  
   864  	ctx2, done, err := wrap(context.Background())
   865  	if err != nil {
   866  		t.Fatal(err)
   867  	}
   868  	defer done(ctx2)
   869  
   870  	extra := []byte("appended bytes")
   871  	size2 := size + int64(len(extra))
   872  	b2 := make([]byte, size2)
   873  	copy(b2[:size], b)
   874  	copy(b2[size:], extra)
   875  	d2 := digest.FromBytes(b2)
   876  
   877  	w, err := cs.Writer(ctx2, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d}))
   878  	if err != nil {
   879  		t.Fatal(err)
   880  	}
   881  	t2 := time.Now()
   882  
   883  	checkStatus(t, w, content.Status{
   884  		Ref:    ref,
   885  		Offset: size,
   886  		Total:  size,
   887  	}, d, t1, t2, t1, t2)
   888  
   889  	if _, err := w.Write(extra); err != nil {
   890  		t.Fatal(err)
   891  	}
   892  
   893  	if err := w.Commit(ctx2, size2, d2); err != nil {
   894  		t.Fatal(err)
   895  	}
   896  	t3 := time.Now()
   897  
   898  	info := content.Info{
   899  		Digest: d,
   900  		Size:   size,
   901  	}
   902  	if err := checkContent(ctx, cs, d, info, t1, t3, t1, t3); err != nil {
   903  		t.Fatal(err)
   904  	}
   905  
   906  	info2 := content.Info{
   907  		Digest: d2,
   908  		Size:   size2,
   909  	}
   910  	if err := checkContent(ctx2, cs, d2, info2, t1, t3, t1, t3); err != nil {
   911  		t.Fatal(err)
   912  	}
   913  
   914  }
   915  
   916  func checkCrossNSIsolate(ctx context.Context, t *testing.T, cs content.Store) {
   917  	wrap, ok := ctx.Value(wrapperKey{}).(ContextWrapper)
   918  	if !ok {
   919  		t.Skip("multiple contexts not supported")
   920  	}
   921  
   922  	var size int64 = 1000
   923  	b, d := createContent(size)
   924  	ref := fmt.Sprintf("ref-%d", size)
   925  	t1 := time.Now()
   926  
   927  	if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(b), ocispec.Descriptor{Size: size, Digest: d}); err != nil {
   928  		t.Fatal(err)
   929  	}
   930  	t2 := time.Now()
   931  
   932  	ctx2, done, err := wrap(context.Background())
   933  	if err != nil {
   934  		t.Fatal(err)
   935  	}
   936  	defer done(ctx2)
   937  
   938  	t3 := time.Now()
   939  	w, err := cs.Writer(ctx2, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d}))
   940  	if err != nil {
   941  		t.Fatal(err)
   942  	}
   943  	t4 := time.Now()
   944  
   945  	checkNewlyCreated(t, w, t1, t2, t3, t4)
   946  }
   947  
   948  func checkStatus(t *testing.T, w content.Writer, expected content.Status, d digest.Digest, preStart, postStart, preUpdate, postUpdate time.Time) {
   949  	t.Helper()
   950  	st, err := w.Status()
   951  	if err != nil {
   952  		t.Fatalf("failed to get status: %v", err)
   953  	}
   954  
   955  	wd := w.Digest()
   956  	if wd != d {
   957  		t.Fatalf("unexpected digest %v, expected %v", wd, d)
   958  	}
   959  
   960  	if st.Ref != expected.Ref {
   961  		t.Fatalf("unexpected ref %q, expected %q", st.Ref, expected.Ref)
   962  	}
   963  
   964  	if st.Offset != expected.Offset {
   965  		t.Fatalf("unexpected offset %d, expected %d", st.Offset, expected.Offset)
   966  	}
   967  
   968  	if st.Total != expected.Total {
   969  		t.Fatalf("unexpected total %d, expected %d", st.Total, expected.Total)
   970  	}
   971  
   972  	// TODO: Add this test once all implementations guarantee this value is held
   973  	//if st.Expected != expected.Expected {
   974  	//	t.Fatalf("unexpected \"expected digest\" %q, expected %q", st.Expected, expected.Expected)
   975  	//}
   976  
   977  	// FIXME: broken on windows: unexpected updated at time 2017-11-14 13:43:22.178013 -0800 PST,
   978  	// expected between 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 and
   979  	// 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300
   980  	if runtime.GOOS != "windows" {
   981  		if st.StartedAt.After(postStart) || st.StartedAt.Before(preStart) {
   982  			t.Fatalf("unexpected started at time %s, expected between %s and %s", st.StartedAt, preStart, postStart)
   983  		}
   984  
   985  		t.Logf("compare update %v against (%v, %v)", st.UpdatedAt, preUpdate, postUpdate)
   986  		if st.UpdatedAt.After(postUpdate) || st.UpdatedAt.Before(preUpdate) {
   987  			t.Fatalf("unexpected updated at time %s, expected between %s and %s", st.UpdatedAt, preUpdate, postUpdate)
   988  		}
   989  	}
   990  }
   991  
   992  func checkNewlyCreated(t *testing.T, w content.Writer, preStart, postStart, preUpdate, postUpdate time.Time) {
   993  	t.Helper()
   994  	st, err := w.Status()
   995  	if err != nil {
   996  		t.Fatalf("failed to get status: %v", err)
   997  	}
   998  
   999  	wd := w.Digest()
  1000  	if wd != emptyDigest {
  1001  		t.Fatalf("unexpected digest %v, expected %v", wd, emptyDigest)
  1002  	}
  1003  
  1004  	if st.Offset != 0 {
  1005  		t.Fatalf("unexpected offset %v", st.Offset)
  1006  	}
  1007  
  1008  	if runtime.GOOS != "windows" {
  1009  		if st.StartedAt.After(postUpdate) || st.StartedAt.Before(postStart) {
  1010  			t.Fatalf("unexpected started at time %s, expected between %s and %s", st.StartedAt, postStart, postUpdate)
  1011  		}
  1012  	}
  1013  }
  1014  
  1015  func checkInfo(ctx context.Context, cs content.Store, d digest.Digest, expected content.Info, c1, c2, u1, u2 time.Time) error {
  1016  	info, err := cs.Info(ctx, d)
  1017  	if err != nil {
  1018  		return errors.Wrap(err, "failed to get info")
  1019  	}
  1020  
  1021  	if info.Digest != d {
  1022  		return errors.Errorf("unexpected info digest %s, expected %s", info.Digest, d)
  1023  	}
  1024  
  1025  	if info.Size != expected.Size {
  1026  		return errors.Errorf("unexpected info size %d, expected %d", info.Size, expected.Size)
  1027  	}
  1028  
  1029  	if info.CreatedAt.After(c2) || info.CreatedAt.Before(c1) {
  1030  		return errors.Errorf("unexpected created at time %s, expected between %s and %s", info.CreatedAt, c1, c2)
  1031  	}
  1032  	// FIXME: broken on windows: unexpected updated at time 2017-11-14 13:43:22.178013 -0800 PST,
  1033  	// expected between 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 and
  1034  	// 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300
  1035  	if runtime.GOOS != "windows" && (info.UpdatedAt.After(u2) || info.UpdatedAt.Before(u1)) {
  1036  		return errors.Errorf("unexpected updated at time %s, expected between %s and %s", info.UpdatedAt, u1, u2)
  1037  	}
  1038  
  1039  	if len(info.Labels) != len(expected.Labels) {
  1040  		return errors.Errorf("mismatched number of labels\ngot:\n%#v\nexpected:\n%#v", info.Labels, expected.Labels)
  1041  	}
  1042  
  1043  	for k, v := range expected.Labels {
  1044  		actual := info.Labels[k]
  1045  		if v != actual {
  1046  			return errors.Errorf("unexpected value for label %q: %q, expected %q", k, actual, v)
  1047  		}
  1048  	}
  1049  
  1050  	return nil
  1051  }
  1052  func checkContent(ctx context.Context, cs content.Store, d digest.Digest, expected content.Info, c1, c2, u1, u2 time.Time) error {
  1053  	if err := checkInfo(ctx, cs, d, expected, c1, c2, u1, u2); err != nil {
  1054  		return err
  1055  	}
  1056  
  1057  	b, err := content.ReadBlob(ctx, cs, ocispec.Descriptor{Digest: d})
  1058  	if err != nil {
  1059  		return errors.Wrap(err, "failed to read blob")
  1060  	}
  1061  
  1062  	if int64(len(b)) != expected.Size {
  1063  		return errors.Errorf("wrong blob size %d, expected %d", len(b), expected.Size)
  1064  	}
  1065  
  1066  	actual := digest.FromBytes(b)
  1067  	if actual != d {
  1068  		return errors.Errorf("wrong digest %s, expected %s", actual, d)
  1069  	}
  1070  
  1071  	return nil
  1072  }
  1073  
  1074  var contentSeed int64
  1075  
  1076  func createContent(size int64) ([]byte, digest.Digest) {
  1077  	// each time we call this, we want to get a different seed, but it should
  1078  	// be related to the initialization order and fairly consistent between
  1079  	// test runs. An atomic integer works just good enough for this.
  1080  	seed := atomic.AddInt64(&contentSeed, 1)
  1081  
  1082  	b, err := ioutil.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), size))
  1083  	if err != nil {
  1084  		panic(err)
  1085  	}
  1086  	return b, digest.FromBytes(b)
  1087  }