github.com/demonoid81/containerd@v1.3.4/metadata/gc_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  	"io"
    22  	"io/ioutil"
    23  	"math/rand"
    24  	"os"
    25  	"path/filepath"
    26  	"sort"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/containerd/containerd/gc"
    31  	"github.com/containerd/containerd/metadata/boltutil"
    32  	digest "github.com/opencontainers/go-digest"
    33  	bolt "go.etcd.io/bbolt"
    34  )
    35  
    36  func TestResourceMax(t *testing.T) {
    37  	if ResourceContent != resourceContentFlat&gc.ResourceMax {
    38  		t.Fatalf("Invalid flat content type: %d (max %d)", resourceContentFlat, gc.ResourceMax)
    39  	}
    40  	if ResourceSnapshot != resourceSnapshotFlat&gc.ResourceMax {
    41  		t.Fatalf("Invalid flat snapshot type: %d (max %d)", resourceSnapshotFlat, gc.ResourceMax)
    42  	}
    43  }
    44  
    45  func TestGCRoots(t *testing.T) {
    46  	db, cleanup, err := newDatabase()
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	defer cleanup()
    51  
    52  	alters := []alterFunc{
    53  		addImage("ns1", "image1", dgst(1), nil),
    54  		addImage("ns1", "image2", dgst(2), labelmap(string(labelGCSnapRef)+"overlay", "sn2")),
    55  		addImage("ns2", "image3", dgst(10), labelmap(string(labelGCContentRef), dgst(11).String())),
    56  		addContainer("ns1", "container1", "overlay", "sn4", nil),
    57  		addContainer("ns1", "container2", "overlay", "sn5", labelmap(string(labelGCSnapRef)+"overlay", "sn6")),
    58  		addContainer("ns1", "container3", "overlay", "sn7", labelmap(
    59  			string(labelGCSnapRef)+"overlay/anything-1", "sn8",
    60  			string(labelGCSnapRef)+"overlay/anything-2", "sn9",
    61  			string(labelGCContentRef), dgst(7).String())),
    62  		addContainer("ns1", "container4", "", "", labelmap(
    63  			string(labelGCContentRef)+".0", dgst(8).String(),
    64  			string(labelGCContentRef)+".1", dgst(9).String())),
    65  		addContent("ns1", dgst(1), nil),
    66  		addContent("ns1", dgst(2), nil),
    67  		addContent("ns1", dgst(3), nil),
    68  		addContent("ns2", dgst(1), nil),
    69  		addContent("ns2", dgst(2), labelmap(string(labelGCRoot), "always")),
    70  		addContent("ns2", dgst(8), nil),
    71  		addContent("ns2", dgst(9), nil),
    72  		addIngest("ns1", "ingest-1", "", nil),       // will be seen as expired
    73  		addIngest("ns1", "ingest-2", "", timeIn(0)), // expired
    74  		addIngest("ns1", "ingest-3", "", timeIn(time.Hour)),
    75  		addIngest("ns2", "ingest-4", "", nil),
    76  		addIngest("ns2", "ingest-5", dgst(8), nil),
    77  		addIngest("ns2", "ingest-6", "", nil),      // added to expired lease
    78  		addIngest("ns2", "ingest-7", dgst(9), nil), // added to expired lease
    79  		addSnapshot("ns1", "overlay", "sn1", "", nil),
    80  		addSnapshot("ns1", "overlay", "sn2", "", nil),
    81  		addSnapshot("ns1", "overlay", "sn3", "", labelmap(string(labelGCRoot), "always")),
    82  		addSnapshot("ns1", "overlay", "sn4", "", nil),
    83  		addSnapshot("ns1", "overlay", "sn5", "", nil),
    84  		addSnapshot("ns1", "overlay", "sn6", "", nil),
    85  		addSnapshot("ns1", "overlay", "sn7", "", nil),
    86  		addSnapshot("ns1", "overlay", "sn8", "", nil),
    87  		addSnapshot("ns1", "overlay", "sn9", "", nil),
    88  		addLeaseSnapshot("ns2", "l1", "overlay", "sn5"),
    89  		addLeaseSnapshot("ns2", "l2", "overlay", "sn6"),
    90  		addLeaseContent("ns2", "l1", dgst(4)),
    91  		addLeaseContent("ns2", "l2", dgst(5)),
    92  		addLease("ns2", "l3", labelmap(string(labelGCExpire), time.Now().Add(time.Hour).Format(time.RFC3339))),
    93  		addLeaseContent("ns2", "l3", dgst(6)),
    94  		addLeaseSnapshot("ns2", "l3", "overlay", "sn7"),
    95  		addLeaseIngest("ns2", "l3", "ingest-4"),
    96  		addLeaseIngest("ns2", "l3", "ingest-5"),
    97  		addLease("ns2", "l4", labelmap(string(labelGCExpire), time.Now().Format(time.RFC3339))),
    98  		addLeaseContent("ns2", "l4", dgst(7)),
    99  		addLeaseSnapshot("ns2", "l4", "overlay", "sn8"),
   100  		addLeaseIngest("ns2", "l4", "ingest-6"),
   101  		addLeaseIngest("ns2", "l4", "ingest-7"),
   102  
   103  		addLease("ns3", "l1", labelmap(string(labelGCFlat), time.Now().Add(time.Hour).Format(time.RFC3339))),
   104  		addLeaseContent("ns3", "l1", dgst(1)),
   105  		addLeaseSnapshot("ns3", "l1", "overlay", "sn1"),
   106  		addLeaseIngest("ns3", "l1", "ingest-1"),
   107  	}
   108  
   109  	expected := []gc.Node{
   110  		gcnode(ResourceContent, "ns1", dgst(1).String()),
   111  		gcnode(ResourceContent, "ns1", dgst(2).String()),
   112  		gcnode(ResourceContent, "ns1", dgst(7).String()),
   113  		gcnode(ResourceContent, "ns1", dgst(8).String()),
   114  		gcnode(ResourceContent, "ns1", dgst(9).String()),
   115  		gcnode(ResourceContent, "ns2", dgst(2).String()),
   116  		gcnode(ResourceContent, "ns2", dgst(4).String()),
   117  		gcnode(ResourceContent, "ns2", dgst(5).String()),
   118  		gcnode(ResourceContent, "ns2", dgst(6).String()),
   119  		gcnode(ResourceContent, "ns2", dgst(10).String()),
   120  		gcnode(ResourceContent, "ns2", dgst(11).String()),
   121  		gcnode(ResourceSnapshot, "ns1", "overlay/sn2"),
   122  		gcnode(ResourceSnapshot, "ns1", "overlay/sn3"),
   123  		gcnode(ResourceSnapshot, "ns1", "overlay/sn4"),
   124  		gcnode(ResourceSnapshot, "ns1", "overlay/sn5"),
   125  		gcnode(ResourceSnapshot, "ns1", "overlay/sn6"),
   126  		gcnode(ResourceSnapshot, "ns1", "overlay/sn7"),
   127  		gcnode(ResourceSnapshot, "ns1", "overlay/sn8"),
   128  		gcnode(ResourceSnapshot, "ns1", "overlay/sn9"),
   129  		gcnode(ResourceSnapshot, "ns2", "overlay/sn5"),
   130  		gcnode(ResourceSnapshot, "ns2", "overlay/sn6"),
   131  		gcnode(ResourceSnapshot, "ns2", "overlay/sn7"),
   132  		gcnode(ResourceLease, "ns2", "l1"),
   133  		gcnode(ResourceLease, "ns2", "l2"),
   134  		gcnode(ResourceLease, "ns2", "l3"),
   135  		gcnode(ResourceIngest, "ns1", "ingest-3"),
   136  		gcnode(ResourceIngest, "ns2", "ingest-4"),
   137  		gcnode(ResourceIngest, "ns2", "ingest-5"),
   138  		gcnode(ResourceLease, "ns3", "l1"),
   139  		gcnode(ResourceIngest, "ns3", "ingest-1"),
   140  		gcnode(resourceContentFlat, "ns3", dgst(1).String()),
   141  		gcnode(resourceSnapshotFlat, "ns3", "overlay/sn1"),
   142  	}
   143  
   144  	if err := db.Update(func(tx *bolt.Tx) error {
   145  		v1bkt, err := tx.CreateBucketIfNotExists(bucketKeyVersion)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		for _, alter := range alters {
   150  			if err := alter(v1bkt); err != nil {
   151  				return err
   152  			}
   153  		}
   154  		return nil
   155  	}); err != nil {
   156  		t.Fatalf("Update failed: %+v", err)
   157  	}
   158  
   159  	ctx := context.Background()
   160  
   161  	checkNodeC(ctx, t, db, expected, func(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
   162  		return scanRoots(ctx, tx, nc)
   163  	})
   164  }
   165  
   166  func TestGCRemove(t *testing.T) {
   167  	db, cleanup, err := newDatabase()
   168  	if err != nil {
   169  		t.Fatal(err)
   170  	}
   171  	defer cleanup()
   172  
   173  	alters := []alterFunc{
   174  		addImage("ns1", "image1", dgst(1), nil),
   175  		addImage("ns1", "image2", dgst(2), labelmap(string(labelGCSnapRef)+"overlay", "sn2")),
   176  		addContainer("ns1", "container1", "overlay", "sn4", nil),
   177  		addContent("ns1", dgst(1), nil),
   178  		addContent("ns1", dgst(2), nil),
   179  		addContent("ns1", dgst(3), nil),
   180  		addContent("ns2", dgst(1), nil),
   181  		addContent("ns2", dgst(2), labelmap(string(labelGCRoot), "always")),
   182  		addIngest("ns1", "ingest-1", "", nil),
   183  		addIngest("ns2", "ingest-2", "", timeIn(0)),
   184  		addSnapshot("ns1", "overlay", "sn1", "", nil),
   185  		addSnapshot("ns1", "overlay", "sn2", "", nil),
   186  		addSnapshot("ns1", "overlay", "sn3", "", labelmap(string(labelGCRoot), "always")),
   187  		addSnapshot("ns1", "overlay", "sn4", "", nil),
   188  		addSnapshot("ns2", "overlay", "sn1", "", nil),
   189  		addLease("ns1", "l1", labelmap(string(labelGCExpire), time.Now().Add(time.Hour).Format(time.RFC3339))),
   190  		addLease("ns2", "l2", labelmap(string(labelGCExpire), time.Now().Format(time.RFC3339))),
   191  	}
   192  
   193  	all := []gc.Node{
   194  		gcnode(ResourceContent, "ns1", dgst(1).String()),
   195  		gcnode(ResourceContent, "ns1", dgst(2).String()),
   196  		gcnode(ResourceContent, "ns1", dgst(3).String()),
   197  		gcnode(ResourceContent, "ns2", dgst(1).String()),
   198  		gcnode(ResourceContent, "ns2", dgst(2).String()),
   199  		gcnode(ResourceSnapshot, "ns1", "overlay/sn1"),
   200  		gcnode(ResourceSnapshot, "ns1", "overlay/sn2"),
   201  		gcnode(ResourceSnapshot, "ns1", "overlay/sn3"),
   202  		gcnode(ResourceSnapshot, "ns1", "overlay/sn4"),
   203  		gcnode(ResourceSnapshot, "ns2", "overlay/sn1"),
   204  		gcnode(ResourceLease, "ns1", "l1"),
   205  		gcnode(ResourceLease, "ns2", "l2"),
   206  		gcnode(ResourceIngest, "ns1", "ingest-1"),
   207  		gcnode(ResourceIngest, "ns2", "ingest-2"),
   208  	}
   209  
   210  	var deleted, remaining []gc.Node
   211  	for i, n := range all {
   212  		if i%2 == 0 {
   213  			deleted = append(deleted, n)
   214  		} else {
   215  			remaining = append(remaining, n)
   216  		}
   217  	}
   218  
   219  	if err := db.Update(func(tx *bolt.Tx) error {
   220  		v1bkt, err := tx.CreateBucketIfNotExists(bucketKeyVersion)
   221  		if err != nil {
   222  			return err
   223  		}
   224  		for _, alter := range alters {
   225  			if err := alter(v1bkt); err != nil {
   226  				return err
   227  			}
   228  		}
   229  		return nil
   230  	}); err != nil {
   231  		t.Fatalf("Update failed: %+v", err)
   232  	}
   233  
   234  	ctx := context.Background()
   235  
   236  	checkNodes(ctx, t, db, all, func(ctx context.Context, tx *bolt.Tx, fn func(context.Context, gc.Node) error) error {
   237  		return scanAll(ctx, tx, fn)
   238  	})
   239  	if t.Failed() {
   240  		t.Fatal("Scan all failed")
   241  	}
   242  
   243  	if err := db.Update(func(tx *bolt.Tx) error {
   244  		for _, n := range deleted {
   245  			if err := remove(ctx, tx, n); err != nil {
   246  				return err
   247  			}
   248  		}
   249  		return nil
   250  	}); err != nil {
   251  		t.Fatalf("Update failed: %+v", err)
   252  	}
   253  
   254  	checkNodes(ctx, t, db, remaining, func(ctx context.Context, tx *bolt.Tx, fn func(context.Context, gc.Node) error) error {
   255  		return scanAll(ctx, tx, fn)
   256  	})
   257  }
   258  
   259  func TestGCRefs(t *testing.T) {
   260  	db, cleanup, err := newDatabase()
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  	defer cleanup()
   265  
   266  	alters := []alterFunc{
   267  		addContent("ns1", dgst(1), nil),
   268  		addContent("ns1", dgst(2), nil),
   269  		addContent("ns1", dgst(3), nil),
   270  		addContent("ns1", dgst(4), labelmap(string(labelGCContentRef), dgst(1).String())),
   271  		addContent("ns1", dgst(5), labelmap(string(labelGCContentRef)+".anything-1", dgst(2).String(), string(labelGCContentRef)+".anything-2", dgst(3).String())),
   272  		addContent("ns1", dgst(6), labelmap(string(labelGCContentRef)+"bad", dgst(1).String())),
   273  		addContent("ns1", dgst(7), labelmap(string(labelGCContentRef)+"/anything-1", dgst(2).String(), string(labelGCContentRef)+"/anything-2", dgst(3).String())),
   274  		addContent("ns2", dgst(1), nil),
   275  		addContent("ns2", dgst(2), nil),
   276  		addIngest("ns1", "ingest-1", "", nil),
   277  		addIngest("ns2", "ingest-2", dgst(8), nil),
   278  		addSnapshot("ns1", "overlay", "sn1", "", nil),
   279  		addSnapshot("ns1", "overlay", "sn2", "sn1", nil),
   280  		addSnapshot("ns1", "overlay", "sn3", "sn2", nil),
   281  		addSnapshot("ns1", "overlay", "sn4", "", labelmap(string(labelGCSnapRef)+"btrfs", "sn1", string(labelGCSnapRef)+"overlay", "sn1")),
   282  		addSnapshot("ns1", "overlay", "sn5", "", labelmap(string(labelGCSnapRef)+"overlay/anything-1", "sn1", string(labelGCSnapRef)+"overlay/anything-2", "sn2")),
   283  		addSnapshot("ns1", "btrfs", "sn1", "", nil),
   284  		addSnapshot("ns2", "overlay", "sn1", "", nil),
   285  		addSnapshot("ns2", "overlay", "sn2", "sn1", nil),
   286  		addSnapshot("ns2", "overlay", "sn3", "", labelmap(
   287  			string(labelGCContentRef), dgst(1).String(),
   288  			string(labelGCContentRef)+".keep-me", dgst(6).String())),
   289  
   290  		// Test flat references don't follow label references
   291  		addContent("ns3", dgst(1), nil),
   292  		addContent("ns3", dgst(2), labelmap(string(labelGCContentRef)+".0", dgst(1).String())),
   293  
   294  		addSnapshot("ns3", "overlay", "sn1", "", nil),
   295  		addSnapshot("ns3", "overlay", "sn2", "sn1", nil),
   296  		addSnapshot("ns3", "overlay", "sn3", "", labelmap(string(labelGCSnapRef)+"btrfs", "sn1", string(labelGCSnapRef)+"overlay", "sn1")),
   297  	}
   298  
   299  	refs := map[gc.Node][]gc.Node{
   300  		gcnode(ResourceContent, "ns1", dgst(1).String()): nil,
   301  		gcnode(ResourceContent, "ns1", dgst(2).String()): nil,
   302  		gcnode(ResourceContent, "ns1", dgst(3).String()): nil,
   303  		gcnode(ResourceContent, "ns1", dgst(4).String()): {
   304  			gcnode(ResourceContent, "ns1", dgst(1).String()),
   305  		},
   306  		gcnode(ResourceContent, "ns1", dgst(5).String()): {
   307  			gcnode(ResourceContent, "ns1", dgst(2).String()),
   308  			gcnode(ResourceContent, "ns1", dgst(3).String()),
   309  		},
   310  		gcnode(ResourceContent, "ns1", dgst(6).String()): nil,
   311  		gcnode(ResourceContent, "ns1", dgst(7).String()): {
   312  			gcnode(ResourceContent, "ns1", dgst(2).String()),
   313  			gcnode(ResourceContent, "ns1", dgst(3).String()),
   314  		},
   315  		gcnode(ResourceContent, "ns2", dgst(1).String()): nil,
   316  		gcnode(ResourceContent, "ns2", dgst(2).String()): nil,
   317  		gcnode(ResourceSnapshot, "ns1", "overlay/sn1"):   nil,
   318  		gcnode(ResourceSnapshot, "ns1", "overlay/sn2"): {
   319  			gcnode(ResourceSnapshot, "ns1", "overlay/sn1"),
   320  		},
   321  		gcnode(ResourceSnapshot, "ns1", "overlay/sn3"): {
   322  			gcnode(ResourceSnapshot, "ns1", "overlay/sn2"),
   323  		},
   324  		gcnode(ResourceSnapshot, "ns1", "overlay/sn4"): {
   325  			gcnode(ResourceSnapshot, "ns1", "btrfs/sn1"),
   326  			gcnode(ResourceSnapshot, "ns1", "overlay/sn1"),
   327  		},
   328  		gcnode(ResourceSnapshot, "ns1", "overlay/sn5"): {
   329  			gcnode(ResourceSnapshot, "ns1", "overlay/sn1"),
   330  			gcnode(ResourceSnapshot, "ns1", "overlay/sn2"),
   331  		},
   332  		gcnode(ResourceSnapshot, "ns1", "btrfs/sn1"):   nil,
   333  		gcnode(ResourceSnapshot, "ns2", "overlay/sn1"): nil,
   334  		gcnode(ResourceSnapshot, "ns2", "overlay/sn2"): {
   335  			gcnode(ResourceSnapshot, "ns2", "overlay/sn1"),
   336  		},
   337  		gcnode(ResourceSnapshot, "ns2", "overlay/sn3"): {
   338  			gcnode(ResourceContent, "ns2", dgst(1).String()),
   339  			gcnode(ResourceContent, "ns2", dgst(6).String()),
   340  		},
   341  		gcnode(ResourceIngest, "ns1", "ingest-1"): nil,
   342  		gcnode(ResourceIngest, "ns2", "ingest-2"): {
   343  			gcnode(ResourceContent, "ns2", dgst(8).String()),
   344  		},
   345  		gcnode(resourceSnapshotFlat, "ns3", "overlay/sn2"): {
   346  			gcnode(resourceSnapshotFlat, "ns3", "overlay/sn1"),
   347  		},
   348  		gcnode(ResourceSnapshot, "ns3", "overlay/sn2"): {
   349  			gcnode(ResourceSnapshot, "ns3", "overlay/sn1"),
   350  		},
   351  		gcnode(resourceSnapshotFlat, "ns3", "overlay/sn1"): nil,
   352  		gcnode(resourceSnapshotFlat, "ns3", "overlay/sn3"): nil,
   353  		gcnode(ResourceSnapshot, "ns3", "overlay/sn3"): {
   354  			gcnode(ResourceSnapshot, "ns3", "btrfs/sn1"),
   355  			gcnode(ResourceSnapshot, "ns3", "overlay/sn1"),
   356  		},
   357  	}
   358  
   359  	if err := db.Update(func(tx *bolt.Tx) error {
   360  		v1bkt, err := tx.CreateBucketIfNotExists(bucketKeyVersion)
   361  		if err != nil {
   362  			return err
   363  		}
   364  		for _, alter := range alters {
   365  			if err := alter(v1bkt); err != nil {
   366  				return err
   367  			}
   368  		}
   369  		return nil
   370  	}); err != nil {
   371  		t.Fatalf("Update failed: %+v", err)
   372  	}
   373  
   374  	ctx := context.Background()
   375  
   376  	for n, nodes := range refs {
   377  		checkNodeC(ctx, t, db, nodes, func(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
   378  			return references(ctx, tx, n, func(n gc.Node) {
   379  				select {
   380  				case nc <- n:
   381  				case <-ctx.Done():
   382  				}
   383  			})
   384  		})
   385  		if t.Failed() {
   386  			t.Fatalf("Failure scanning %v", n)
   387  		}
   388  	}
   389  }
   390  
   391  func newDatabase() (*bolt.DB, func(), error) {
   392  	td, err := ioutil.TempDir("", "gc-roots-")
   393  	if err != nil {
   394  		return nil, nil, err
   395  	}
   396  
   397  	db, err := bolt.Open(filepath.Join(td, "test.db"), 0777, nil)
   398  	if err != nil {
   399  		os.RemoveAll(td)
   400  		return nil, nil, err
   401  	}
   402  
   403  	return db, func() {
   404  		db.Close()
   405  		os.RemoveAll(td)
   406  	}, nil
   407  }
   408  
   409  func checkNodeC(ctx context.Context, t *testing.T, db *bolt.DB, expected []gc.Node, fn func(context.Context, *bolt.Tx, chan<- gc.Node) error) {
   410  	var actual []gc.Node
   411  	nc := make(chan gc.Node)
   412  	done := make(chan struct{})
   413  	go func() {
   414  		defer close(done)
   415  		for n := range nc {
   416  			actual = append(actual, n)
   417  		}
   418  	}()
   419  	if err := db.View(func(tx *bolt.Tx) error {
   420  		defer close(nc)
   421  		return fn(ctx, tx, nc)
   422  	}); err != nil {
   423  		t.Fatal(err)
   424  	}
   425  
   426  	<-done
   427  	checkNodesEqual(t, actual, expected)
   428  }
   429  
   430  func checkNodes(ctx context.Context, t *testing.T, db *bolt.DB, expected []gc.Node, fn func(context.Context, *bolt.Tx, func(context.Context, gc.Node) error) error) {
   431  	var actual []gc.Node
   432  	scanFn := func(ctx context.Context, n gc.Node) error {
   433  		actual = append(actual, n)
   434  		return nil
   435  	}
   436  
   437  	if err := db.View(func(tx *bolt.Tx) error {
   438  		return fn(ctx, tx, scanFn)
   439  	}); err != nil {
   440  		t.Fatal(err)
   441  	}
   442  
   443  	checkNodesEqual(t, actual, expected)
   444  }
   445  
   446  func checkNodesEqual(t *testing.T, n1, n2 []gc.Node) {
   447  	sort.Sort(nodeList(n1))
   448  	sort.Sort(nodeList(n2))
   449  
   450  	if len(n1) != len(n2) {
   451  		t.Fatalf("Nodes do not match\n\tExpected:\n\t%v\n\tActual:\n\t%v", n2, n1)
   452  	}
   453  
   454  	for i := range n1 {
   455  		if n1[i] != n2[i] {
   456  			t.Errorf("[%d] root does not match expected: expected %v, got %v", i, n2[i], n1[i])
   457  		}
   458  	}
   459  }
   460  
   461  type nodeList []gc.Node
   462  
   463  func (nodes nodeList) Len() int {
   464  	return len(nodes)
   465  }
   466  
   467  func (nodes nodeList) Less(i, j int) bool {
   468  	if nodes[i].Type != nodes[j].Type {
   469  		return nodes[i].Type < nodes[j].Type
   470  	}
   471  	if nodes[i].Namespace != nodes[j].Namespace {
   472  		return nodes[i].Namespace < nodes[j].Namespace
   473  	}
   474  	return nodes[i].Key < nodes[j].Key
   475  }
   476  
   477  func (nodes nodeList) Swap(i, j int) {
   478  	nodes[i], nodes[j] = nodes[j], nodes[i]
   479  }
   480  
   481  type alterFunc func(bkt *bolt.Bucket) error
   482  
   483  func addImage(ns, name string, dgst digest.Digest, labels map[string]string) alterFunc {
   484  	return func(bkt *bolt.Bucket) error {
   485  		ibkt, err := createBuckets(bkt, ns, string(bucketKeyObjectImages), name)
   486  		if err != nil {
   487  			return err
   488  		}
   489  
   490  		tbkt, err := ibkt.CreateBucket(bucketKeyTarget)
   491  		if err != nil {
   492  			return err
   493  		}
   494  		if err := tbkt.Put(bucketKeyDigest, []byte(dgst.String())); err != nil {
   495  			return err
   496  		}
   497  
   498  		return boltutil.WriteLabels(ibkt, labels)
   499  	}
   500  }
   501  
   502  func addSnapshot(ns, snapshotter, name, parent string, labels map[string]string) alterFunc {
   503  	return func(bkt *bolt.Bucket) error {
   504  		sbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectSnapshots), snapshotter, name)
   505  		if err != nil {
   506  			return err
   507  		}
   508  		if parent != "" {
   509  			if err := sbkt.Put(bucketKeyParent, []byte(parent)); err != nil {
   510  				return err
   511  			}
   512  		}
   513  		return boltutil.WriteLabels(sbkt, labels)
   514  	}
   515  }
   516  
   517  func addContent(ns string, dgst digest.Digest, labels map[string]string) alterFunc {
   518  	return func(bkt *bolt.Bucket) error {
   519  		cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectContent), string(bucketKeyObjectBlob), dgst.String())
   520  		if err != nil {
   521  			return err
   522  		}
   523  		return boltutil.WriteLabels(cbkt, labels)
   524  	}
   525  }
   526  
   527  func addIngest(ns, ref string, expected digest.Digest, expires *time.Time) alterFunc {
   528  	return func(bkt *bolt.Bucket) error {
   529  		cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectContent), string(bucketKeyObjectIngests), ref)
   530  		if err != nil {
   531  			return err
   532  		}
   533  		if expected != "" {
   534  			if err := cbkt.Put(bucketKeyExpected, []byte(expected)); err != nil {
   535  				return err
   536  			}
   537  		}
   538  		if expires != nil {
   539  			if err := writeExpireAt(*expires, cbkt); err != nil {
   540  				return err
   541  			}
   542  		}
   543  		return nil
   544  	}
   545  }
   546  
   547  func addLease(ns, lid string, labels map[string]string) alterFunc {
   548  	return func(bkt *bolt.Bucket) error {
   549  		lbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid)
   550  		if err != nil {
   551  			return err
   552  		}
   553  		return boltutil.WriteLabels(lbkt, labels)
   554  	}
   555  }
   556  
   557  func addLeaseSnapshot(ns, lid, snapshotter, name string) alterFunc {
   558  	return func(bkt *bolt.Bucket) error {
   559  		sbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid, string(bucketKeyObjectSnapshots), snapshotter)
   560  		if err != nil {
   561  			return err
   562  		}
   563  		return sbkt.Put([]byte(name), nil)
   564  	}
   565  }
   566  
   567  func addLeaseContent(ns, lid string, dgst digest.Digest) alterFunc {
   568  	return func(bkt *bolt.Bucket) error {
   569  		cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid, string(bucketKeyObjectContent))
   570  		if err != nil {
   571  			return err
   572  		}
   573  		return cbkt.Put([]byte(dgst.String()), nil)
   574  	}
   575  }
   576  
   577  func addLeaseIngest(ns, lid, ref string) alterFunc {
   578  	return func(bkt *bolt.Bucket) error {
   579  		cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectLeases), lid, string(bucketKeyObjectIngests))
   580  		if err != nil {
   581  			return err
   582  		}
   583  		return cbkt.Put([]byte(ref), nil)
   584  	}
   585  }
   586  
   587  func addContainer(ns, name, snapshotter, snapshot string, labels map[string]string) alterFunc {
   588  	return func(bkt *bolt.Bucket) error {
   589  		cbkt, err := createBuckets(bkt, ns, string(bucketKeyObjectContainers), name)
   590  		if err != nil {
   591  			return err
   592  		}
   593  		if err := cbkt.Put(bucketKeySnapshotter, []byte(snapshotter)); err != nil {
   594  			return err
   595  		}
   596  		if err := cbkt.Put(bucketKeySnapshotKey, []byte(snapshot)); err != nil {
   597  			return err
   598  		}
   599  		return boltutil.WriteLabels(cbkt, labels)
   600  	}
   601  }
   602  
   603  func createBuckets(bkt *bolt.Bucket, names ...string) (*bolt.Bucket, error) {
   604  	for _, name := range names {
   605  		nbkt, err := bkt.CreateBucketIfNotExists([]byte(name))
   606  		if err != nil {
   607  			return nil, err
   608  		}
   609  		bkt = nbkt
   610  	}
   611  	return bkt, nil
   612  }
   613  
   614  func labelmap(kv ...string) map[string]string {
   615  	if len(kv)%2 != 0 {
   616  		panic("bad labels argument")
   617  	}
   618  	l := map[string]string{}
   619  	for i := 0; i < len(kv); i = i + 2 {
   620  		l[kv[i]] = kv[i+1]
   621  	}
   622  	return l
   623  }
   624  
   625  func dgst(i int64) digest.Digest {
   626  	r := rand.New(rand.NewSource(i))
   627  	dgstr := digest.SHA256.Digester()
   628  	if _, err := io.CopyN(dgstr.Hash(), r, 256); err != nil {
   629  		panic(err)
   630  	}
   631  	return dgstr.Digest()
   632  }
   633  
   634  func timeIn(d time.Duration) *time.Time {
   635  	t := time.Now().UTC().Add(d)
   636  	return &t
   637  }