github.com/lalkh/containerd@v1.4.3/metadata/db_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 metadata
    18  
    19  import (
    20  	"context"
    21  	"encoding/binary"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"math/rand"
    26  	"os"
    27  	"path/filepath"
    28  	"runtime/pprof"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	"github.com/containerd/containerd/containers"
    34  	"github.com/containerd/containerd/content"
    35  	"github.com/containerd/containerd/content/local"
    36  	"github.com/containerd/containerd/errdefs"
    37  	"github.com/containerd/containerd/gc"
    38  	"github.com/containerd/containerd/images"
    39  	"github.com/containerd/containerd/leases"
    40  	"github.com/containerd/containerd/log/logtest"
    41  	"github.com/containerd/containerd/namespaces"
    42  	"github.com/containerd/containerd/snapshots"
    43  	"github.com/containerd/containerd/snapshots/native"
    44  	"github.com/gogo/protobuf/types"
    45  	digest "github.com/opencontainers/go-digest"
    46  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    47  	"github.com/pkg/errors"
    48  	bolt "go.etcd.io/bbolt"
    49  )
    50  
    51  type testOptions struct {
    52  	extraSnapshots map[string]func(string) (snapshots.Snapshotter, error)
    53  }
    54  
    55  type testOpt func(*testOptions)
    56  
    57  func withSnapshotter(name string, fn func(string) (snapshots.Snapshotter, error)) testOpt {
    58  	return func(to *testOptions) {
    59  		if to.extraSnapshots == nil {
    60  			to.extraSnapshots = map[string]func(string) (snapshots.Snapshotter, error){}
    61  		}
    62  		to.extraSnapshots[name] = fn
    63  	}
    64  }
    65  
    66  func testDB(t *testing.T, opt ...testOpt) (context.Context, *DB, func()) {
    67  	ctx, cancel := context.WithCancel(context.Background())
    68  	ctx = namespaces.WithNamespace(ctx, "testing")
    69  	ctx = logtest.WithT(ctx, t)
    70  
    71  	var topts testOptions
    72  
    73  	for _, o := range opt {
    74  		o(&topts)
    75  	}
    76  
    77  	dirname, err := ioutil.TempDir("", strings.Replace(t.Name(), "/", "_", -1)+"-")
    78  	if err != nil {
    79  		t.Fatal(err)
    80  	}
    81  
    82  	snapshotter, err := native.NewSnapshotter(filepath.Join(dirname, "native"))
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  
    87  	snapshotters := map[string]snapshots.Snapshotter{
    88  		"native": snapshotter,
    89  	}
    90  
    91  	for name, fn := range topts.extraSnapshots {
    92  		snapshotter, err := fn(filepath.Join(dirname, name))
    93  		if err != nil {
    94  			t.Fatal(err)
    95  		}
    96  		snapshotters[name] = snapshotter
    97  	}
    98  
    99  	cs, err := local.NewStore(filepath.Join(dirname, "content"))
   100  	if err != nil {
   101  		t.Fatal(err)
   102  	}
   103  
   104  	bdb, err := bolt.Open(filepath.Join(dirname, "metadata.db"), 0644, nil)
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  
   109  	db := NewDB(bdb, cs, snapshotters)
   110  	if err := db.Init(ctx); err != nil {
   111  		t.Fatal(err)
   112  	}
   113  
   114  	return ctx, db, func() {
   115  		bdb.Close()
   116  		if err := os.RemoveAll(dirname); err != nil {
   117  			t.Log("failed removing temp dir", err)
   118  		}
   119  		cancel()
   120  	}
   121  }
   122  
   123  func TestInit(t *testing.T) {
   124  	ctx, db, cancel := testEnv(t)
   125  	defer cancel()
   126  
   127  	if err := NewDB(db, nil, nil).Init(ctx); err != nil {
   128  		t.Fatal(err)
   129  	}
   130  
   131  	version, err := readDBVersion(db, bucketKeyVersion)
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  	if version != dbVersion {
   136  		t.Fatalf("Unexpected version %d, expected %d", version, dbVersion)
   137  	}
   138  }
   139  
   140  func TestMigrations(t *testing.T) {
   141  	testRefs := []struct {
   142  		ref  string
   143  		bref string
   144  	}{
   145  		{
   146  			ref:  "k1",
   147  			bref: "bk1",
   148  		},
   149  		{
   150  			ref:  strings.Repeat("longerkey", 30), // 270 characters
   151  			bref: "short",
   152  		},
   153  		{
   154  			ref:  "short",
   155  			bref: strings.Repeat("longerkey", 30), // 270 characters
   156  		},
   157  		{
   158  			ref:  "emptykey",
   159  			bref: "",
   160  		},
   161  	}
   162  	migrationTests := []struct {
   163  		name  string
   164  		init  func(*bolt.Tx) error
   165  		check func(*bolt.Tx) error
   166  	}{
   167  		{
   168  			name: "ChildrenKey",
   169  			init: func(tx *bolt.Tx) error {
   170  				bkt, err := createSnapshotterBucket(tx, "testing", "testing")
   171  				if err != nil {
   172  					return err
   173  				}
   174  
   175  				snapshots := []struct {
   176  					key    string
   177  					parent string
   178  				}{
   179  					{
   180  						key:    "k1",
   181  						parent: "",
   182  					},
   183  					{
   184  						key:    "k2",
   185  						parent: "k1",
   186  					},
   187  					{
   188  						key:    "k2a",
   189  						parent: "k1",
   190  					},
   191  					{
   192  						key:    "a1",
   193  						parent: "k2",
   194  					},
   195  				}
   196  
   197  				for _, s := range snapshots {
   198  					sbkt, err := bkt.CreateBucket([]byte(s.key))
   199  					if err != nil {
   200  						return err
   201  					}
   202  					if err := sbkt.Put(bucketKeyParent, []byte(s.parent)); err != nil {
   203  						return err
   204  					}
   205  				}
   206  
   207  				return nil
   208  			},
   209  			check: func(tx *bolt.Tx) error {
   210  				bkt := getSnapshotterBucket(tx, "testing", "testing")
   211  				if bkt == nil {
   212  					return errors.Wrap(errdefs.ErrNotFound, "snapshots bucket not found")
   213  				}
   214  				snapshots := []struct {
   215  					key      string
   216  					children []string
   217  				}{
   218  					{
   219  						key:      "k1",
   220  						children: []string{"k2", "k2a"},
   221  					},
   222  					{
   223  						key:      "k2",
   224  						children: []string{"a1"},
   225  					},
   226  					{
   227  						key:      "k2a",
   228  						children: []string{},
   229  					},
   230  					{
   231  						key:      "a1",
   232  						children: []string{},
   233  					},
   234  				}
   235  
   236  				for _, s := range snapshots {
   237  					sbkt := bkt.Bucket([]byte(s.key))
   238  					if sbkt == nil {
   239  						return errors.Wrap(errdefs.ErrNotFound, "key does not exist")
   240  					}
   241  
   242  					cbkt := sbkt.Bucket(bucketKeyChildren)
   243  					var cn int
   244  					if cbkt != nil {
   245  						cn = cbkt.Stats().KeyN
   246  					}
   247  
   248  					if cn != len(s.children) {
   249  						return errors.Errorf("unexpected number of children %d, expected %d", cn, len(s.children))
   250  					}
   251  
   252  					for _, ch := range s.children {
   253  						if v := cbkt.Get([]byte(ch)); v == nil {
   254  							return errors.Errorf("missing child record for %s", ch)
   255  						}
   256  					}
   257  				}
   258  
   259  				return nil
   260  			},
   261  		},
   262  		{
   263  			name: "IngestUpdate",
   264  			init: func(tx *bolt.Tx) error {
   265  				bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte("testing"), bucketKeyObjectContent, deprecatedBucketKeyObjectIngest)
   266  				if err != nil {
   267  					return err
   268  				}
   269  
   270  				for _, s := range testRefs {
   271  					if err := bkt.Put([]byte(s.ref), []byte(s.bref)); err != nil {
   272  						return err
   273  					}
   274  				}
   275  
   276  				return nil
   277  			},
   278  			check: func(tx *bolt.Tx) error {
   279  				bkt := getIngestsBucket(tx, "testing")
   280  				if bkt == nil {
   281  					return errors.Wrap(errdefs.ErrNotFound, "ingests bucket not found")
   282  				}
   283  
   284  				for _, s := range testRefs {
   285  					sbkt := bkt.Bucket([]byte(s.ref))
   286  					if sbkt == nil {
   287  						return errors.Wrap(errdefs.ErrNotFound, "ref does not exist")
   288  					}
   289  
   290  					bref := string(sbkt.Get(bucketKeyRef))
   291  					if bref != s.bref {
   292  						return errors.Errorf("unexpected reference key %q, expected %q", bref, s.bref)
   293  					}
   294  				}
   295  
   296  				dbkt := getBucket(tx, bucketKeyVersion, []byte("testing"), bucketKeyObjectContent, deprecatedBucketKeyObjectIngest)
   297  				if dbkt != nil {
   298  					return errors.New("deprecated ingest bucket still exists")
   299  				}
   300  
   301  				return nil
   302  			},
   303  		},
   304  
   305  		{
   306  			name: "NoOp",
   307  			init: func(tx *bolt.Tx) error {
   308  				return nil
   309  			},
   310  			check: func(tx *bolt.Tx) error {
   311  				return nil
   312  			},
   313  		},
   314  	}
   315  
   316  	if len(migrationTests) != len(migrations) {
   317  		t.Fatal("Each migration must have a test case")
   318  	}
   319  
   320  	for i, mt := range migrationTests {
   321  		t.Run(mt.name, runMigrationTest(i, mt.init, mt.check))
   322  	}
   323  }
   324  
   325  func runMigrationTest(i int, init, check func(*bolt.Tx) error) func(t *testing.T) {
   326  	return func(t *testing.T) {
   327  		_, db, cancel := testEnv(t)
   328  		defer cancel()
   329  
   330  		if err := db.Update(init); err != nil {
   331  			t.Fatal(err)
   332  		}
   333  
   334  		if err := db.Update(migrations[i].migrate); err != nil {
   335  			t.Fatal(err)
   336  		}
   337  
   338  		if err := db.View(check); err != nil {
   339  			t.Fatal(err)
   340  		}
   341  	}
   342  }
   343  
   344  func readDBVersion(db *bolt.DB, schema []byte) (int, error) {
   345  	var version int
   346  	if err := db.View(func(tx *bolt.Tx) error {
   347  		bkt := tx.Bucket(schema)
   348  		if bkt == nil {
   349  			return errors.Wrap(errdefs.ErrNotFound, "no version bucket")
   350  		}
   351  		vb := bkt.Get(bucketKeyDBVersion)
   352  		if vb == nil {
   353  			return errors.Wrap(errdefs.ErrNotFound, "no version value")
   354  		}
   355  		v, _ := binary.Varint(vb)
   356  		version = int(v)
   357  		return nil
   358  	}); err != nil {
   359  		return 0, err
   360  	}
   361  	return version, nil
   362  }
   363  
   364  func TestMetadataCollector(t *testing.T) {
   365  	mdb, cs, sn, cleanup := newStores(t)
   366  	defer cleanup()
   367  
   368  	var (
   369  		ctx = logtest.WithT(context.Background(), t)
   370  
   371  		objects = []object{
   372  			blob(bytesFor(1), true),
   373  			blob(bytesFor(2), false),
   374  			blob(bytesFor(3), true),
   375  			blob(bytesFor(4), false, "containerd.io/gc.root", time.Now().String()),
   376  			newSnapshot("1", "", false, false),
   377  			newSnapshot("2", "1", false, false),
   378  			newSnapshot("3", "2", false, false),
   379  			newSnapshot("4", "3", false, false),
   380  			newSnapshot("5", "3", false, true),
   381  			container("1", "4"),
   382  			image("image-1", digestFor(2)),
   383  
   384  			// Test lease preservation
   385  			blob(bytesFor(5), false, "containerd.io/gc.ref.content.0", digestFor(6).String()),
   386  			blob(bytesFor(6), false),
   387  			blob(bytesFor(7), false),
   388  			newSnapshot("6", "", false, false, "containerd.io/gc.ref.content.0", digestFor(7).String()),
   389  			lease("lease-1", []leases.Resource{
   390  				{
   391  					ID:   digestFor(5).String(),
   392  					Type: "content",
   393  				},
   394  				{
   395  					ID:   "6",
   396  					Type: "snapshots/native",
   397  				},
   398  			}, false),
   399  
   400  			// Test flat lease
   401  			blob(bytesFor(8), false, "containerd.io/gc.ref.content.0", digestFor(9).String()),
   402  			blob(bytesFor(9), true),
   403  			blob(bytesFor(10), true),
   404  			newSnapshot("7", "", false, false, "containerd.io/gc.ref.content.0", digestFor(10).String()),
   405  			newSnapshot("8", "7", false, false),
   406  			newSnapshot("9", "8", false, false),
   407  			lease("lease-2", []leases.Resource{
   408  				{
   409  					ID:   digestFor(8).String(),
   410  					Type: "content",
   411  				},
   412  				{
   413  					ID:   "9",
   414  					Type: "snapshots/native",
   415  				},
   416  			}, false, "containerd.io/gc.flat", time.Now().String()),
   417  		}
   418  		remaining []gc.Node
   419  	)
   420  
   421  	if err := mdb.Update(func(tx *bolt.Tx) error {
   422  		for _, obj := range objects {
   423  			node, err := create(obj, tx, mdb, cs, sn)
   424  			if err != nil {
   425  				return err
   426  			}
   427  			if node != nil {
   428  				remaining = append(remaining, *node)
   429  			}
   430  		}
   431  		return nil
   432  	}); err != nil {
   433  		t.Fatalf("Creation failed: %+v", err)
   434  	}
   435  
   436  	if _, err := mdb.GarbageCollect(ctx); err != nil {
   437  		t.Fatal(err)
   438  	}
   439  
   440  	var actual []gc.Node
   441  
   442  	if err := mdb.View(func(tx *bolt.Tx) error {
   443  		scanFn := func(ctx context.Context, node gc.Node) error {
   444  			actual = append(actual, node)
   445  			return nil
   446  		}
   447  		return scanAll(ctx, tx, scanFn)
   448  	}); err != nil {
   449  		t.Fatal(err)
   450  	}
   451  
   452  	checkNodesEqual(t, actual, remaining)
   453  }
   454  
   455  func BenchmarkGarbageCollect(b *testing.B) {
   456  	b.Run("10-Sets", benchmarkTrigger(10))
   457  	b.Run("100-Sets", benchmarkTrigger(100))
   458  	b.Run("1000-Sets", benchmarkTrigger(1000))
   459  	b.Run("10000-Sets", benchmarkTrigger(10000))
   460  }
   461  
   462  func benchmarkTrigger(n int) func(b *testing.B) {
   463  	return func(b *testing.B) {
   464  		mdb, cs, sn, cleanup := newStores(b)
   465  		defer cleanup()
   466  
   467  		objects := []object{}
   468  
   469  		// TODO: Allow max to be configurable
   470  		for i := 0; i < n; i++ {
   471  			objects = append(objects,
   472  				blob(bytesFor(int64(i)), false),
   473  				image(fmt.Sprintf("image-%d", i), digestFor(int64(i))),
   474  			)
   475  			lastSnapshot := 6
   476  			for j := 0; j <= lastSnapshot; j++ {
   477  				var parent string
   478  				key := fmt.Sprintf("snapshot-%d-%d", i, j)
   479  				if j > 0 {
   480  					parent = fmt.Sprintf("snapshot-%d-%d", i, j-1)
   481  				}
   482  				objects = append(objects, newSnapshot(key, parent, false, false))
   483  			}
   484  			objects = append(objects, container(fmt.Sprintf("container-%d", i), fmt.Sprintf("snapshot-%d-%d", i, lastSnapshot)))
   485  
   486  		}
   487  
   488  		// TODO: Create set of objects for removal
   489  
   490  		var (
   491  			ctx = context.Background()
   492  
   493  			remaining []gc.Node
   494  		)
   495  
   496  		if err := mdb.Update(func(tx *bolt.Tx) error {
   497  			for _, obj := range objects {
   498  				node, err := create(obj, tx, mdb, cs, sn)
   499  				if err != nil {
   500  					return err
   501  				}
   502  				if node != nil {
   503  					remaining = append(remaining, *node)
   504  				}
   505  			}
   506  			return nil
   507  		}); err != nil {
   508  			b.Fatalf("Creation failed: %+v", err)
   509  		}
   510  
   511  		// TODO: reset benchmark
   512  		b.ResetTimer()
   513  		//b.StopTimer()
   514  
   515  		labels := pprof.Labels("worker", "trigger")
   516  		pprof.Do(ctx, labels, func(ctx context.Context) {
   517  			for i := 0; i < b.N; i++ {
   518  
   519  				// TODO: Add removal objects
   520  
   521  				//b.StartTimer()
   522  
   523  				if _, err := mdb.GarbageCollect(ctx); err != nil {
   524  					b.Fatal(err)
   525  				}
   526  
   527  				//b.StopTimer()
   528  
   529  				//var actual []gc.Node
   530  
   531  				//if err := db.View(func(tx *bolt.Tx) error {
   532  				//	nodeC := make(chan gc.Node)
   533  				//	var scanErr error
   534  				//	go func() {
   535  				//		defer close(nodeC)
   536  				//		scanErr = scanAll(ctx, tx, nodeC)
   537  				//	}()
   538  				//	for node := range nodeC {
   539  				//		actual = append(actual, node)
   540  				//	}
   541  				//	return scanErr
   542  				//}); err != nil {
   543  				//	t.Fatal(err)
   544  				//}
   545  
   546  				//checkNodesEqual(t, actual, remaining)
   547  			}
   548  		})
   549  	}
   550  }
   551  
   552  func bytesFor(i int64) []byte {
   553  	r := rand.New(rand.NewSource(i))
   554  	var b [256]byte
   555  	_, err := r.Read(b[:])
   556  	if err != nil {
   557  		panic(err)
   558  	}
   559  	return b[:]
   560  }
   561  
   562  func digestFor(i int64) digest.Digest {
   563  	r := rand.New(rand.NewSource(i))
   564  	dgstr := digest.SHA256.Digester()
   565  	_, err := io.Copy(dgstr.Hash(), io.LimitReader(r, 256))
   566  	if err != nil {
   567  		panic(err)
   568  	}
   569  	return dgstr.Digest()
   570  }
   571  
   572  type object struct {
   573  	data    interface{}
   574  	removed bool
   575  	labels  map[string]string
   576  }
   577  
   578  func create(obj object, tx *bolt.Tx, db *DB, cs content.Store, sn snapshots.Snapshotter) (*gc.Node, error) {
   579  	var (
   580  		node      *gc.Node
   581  		namespace = "test"
   582  		ctx       = WithTransactionContext(namespaces.WithNamespace(context.Background(), namespace), tx)
   583  	)
   584  
   585  	switch v := obj.data.(type) {
   586  	case testContent:
   587  		expected := digest.FromBytes(v.data)
   588  		w, err := cs.Writer(ctx,
   589  			content.WithRef("test-ref"),
   590  			content.WithDescriptor(ocispec.Descriptor{Size: int64(len(v.data)), Digest: expected}))
   591  		if err != nil {
   592  			return nil, errors.Wrap(err, "failed to create writer")
   593  		}
   594  		if _, err := w.Write(v.data); err != nil {
   595  			return nil, errors.Wrap(err, "write blob failed")
   596  		}
   597  		if err := w.Commit(ctx, int64(len(v.data)), expected, content.WithLabels(obj.labels)); err != nil {
   598  			return nil, errors.Wrap(err, "failed to commit blob")
   599  		}
   600  		if !obj.removed {
   601  			node = &gc.Node{
   602  				Type:      ResourceContent,
   603  				Namespace: namespace,
   604  				Key:       expected.String(),
   605  			}
   606  		}
   607  	case testSnapshot:
   608  		if v.active {
   609  			_, err := sn.Prepare(ctx, v.key, v.parent, snapshots.WithLabels(obj.labels))
   610  			if err != nil {
   611  				return nil, err
   612  			}
   613  		} else {
   614  			akey := fmt.Sprintf("%s-active", v.key)
   615  			_, err := sn.Prepare(ctx, akey, v.parent)
   616  			if err != nil {
   617  				return nil, err
   618  			}
   619  			if err := sn.Commit(ctx, v.key, akey, snapshots.WithLabels(obj.labels)); err != nil {
   620  				return nil, err
   621  			}
   622  		}
   623  		if !obj.removed {
   624  			node = &gc.Node{
   625  				Type:      ResourceSnapshot,
   626  				Namespace: namespace,
   627  				Key:       fmt.Sprintf("native/%s", v.key),
   628  			}
   629  		}
   630  	case testImage:
   631  		image := images.Image{
   632  			Name:   v.name,
   633  			Target: v.target,
   634  			Labels: obj.labels,
   635  		}
   636  
   637  		_, err := NewImageStore(db).Create(ctx, image)
   638  		if err != nil {
   639  			return nil, errors.Wrap(err, "failed to create image")
   640  		}
   641  	case testContainer:
   642  		container := containers.Container{
   643  			ID:          v.id,
   644  			SnapshotKey: v.snapshot,
   645  			Snapshotter: "native",
   646  			Labels:      obj.labels,
   647  
   648  			Runtime: containers.RuntimeInfo{
   649  				Name: "testruntime",
   650  			},
   651  			Spec: &types.Any{},
   652  		}
   653  		_, err := NewContainerStore(db).Create(ctx, container)
   654  		if err != nil {
   655  			return nil, err
   656  		}
   657  	case testLease:
   658  		lm := NewLeaseManager(db)
   659  
   660  		l, err := lm.Create(ctx, leases.WithID(v.id), leases.WithLabels(obj.labels))
   661  		if err != nil {
   662  			return nil, err
   663  		}
   664  
   665  		for _, ref := range v.refs {
   666  			if err := lm.AddResource(ctx, l, ref); err != nil {
   667  				return nil, err
   668  			}
   669  		}
   670  
   671  		if !obj.removed {
   672  			node = &gc.Node{
   673  				Type:      ResourceLease,
   674  				Namespace: namespace,
   675  				Key:       v.id,
   676  			}
   677  		}
   678  	}
   679  
   680  	return node, nil
   681  }
   682  
   683  func blob(b []byte, r bool, l ...string) object {
   684  	return object{
   685  		data: testContent{
   686  			data: b,
   687  		},
   688  		removed: r,
   689  		labels:  labelmap(l...),
   690  	}
   691  }
   692  
   693  func image(n string, d digest.Digest, l ...string) object {
   694  	return object{
   695  		data: testImage{
   696  			name: n,
   697  			target: ocispec.Descriptor{
   698  				MediaType: "irrelevant",
   699  				Digest:    d,
   700  				Size:      256,
   701  			},
   702  		},
   703  		removed: false,
   704  		labels:  labelmap(l...),
   705  	}
   706  }
   707  
   708  func newSnapshot(key, parent string, active, r bool, l ...string) object {
   709  	return object{
   710  		data: testSnapshot{
   711  			key:    key,
   712  			parent: parent,
   713  			active: active,
   714  		},
   715  		removed: r,
   716  		labels:  labelmap(l...),
   717  	}
   718  }
   719  
   720  func container(id, s string, l ...string) object {
   721  	return object{
   722  		data: testContainer{
   723  			id:       id,
   724  			snapshot: s,
   725  		},
   726  		removed: false,
   727  		labels:  labelmap(l...),
   728  	}
   729  }
   730  
   731  func lease(id string, refs []leases.Resource, r bool, l ...string) object {
   732  	return object{
   733  		data: testLease{
   734  			id:   id,
   735  			refs: refs,
   736  		},
   737  		removed: r,
   738  		labels:  labelmap(l...),
   739  	}
   740  }
   741  
   742  type testContent struct {
   743  	data []byte
   744  }
   745  
   746  type testSnapshot struct {
   747  	key    string
   748  	parent string
   749  	active bool
   750  }
   751  
   752  type testImage struct {
   753  	name   string
   754  	target ocispec.Descriptor
   755  }
   756  
   757  type testContainer struct {
   758  	id       string
   759  	snapshot string
   760  }
   761  
   762  type testLease struct {
   763  	id   string
   764  	refs []leases.Resource
   765  }
   766  
   767  func newStores(t testing.TB) (*DB, content.Store, snapshots.Snapshotter, func()) {
   768  	td, err := ioutil.TempDir("", "gc-test-")
   769  	if err != nil {
   770  		t.Fatal(err)
   771  	}
   772  	db, err := bolt.Open(filepath.Join(td, "meta.db"), 0644, nil)
   773  	if err != nil {
   774  		t.Fatal(err)
   775  	}
   776  
   777  	nsn, err := native.NewSnapshotter(filepath.Join(td, "snapshots"))
   778  	if err != nil {
   779  		t.Fatal(err)
   780  	}
   781  
   782  	lcs, err := local.NewStore(filepath.Join(td, "content"))
   783  	if err != nil {
   784  		t.Fatal(err)
   785  	}
   786  
   787  	mdb := NewDB(db, lcs, map[string]snapshots.Snapshotter{"native": nsn})
   788  
   789  	return mdb, mdb.ContentStore(), mdb.Snapshotter("native"), func() {
   790  		os.RemoveAll(td)
   791  	}
   792  }