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