github.com/containerd/Containerd@v1.4.13/metadata/gc.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  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/containerd/containerd/gc"
    27  	"github.com/containerd/containerd/log"
    28  	"github.com/pkg/errors"
    29  	bolt "go.etcd.io/bbolt"
    30  )
    31  
    32  const (
    33  	// ResourceUnknown specifies an unknown resource
    34  	ResourceUnknown gc.ResourceType = iota
    35  	// ResourceContent specifies a content resource
    36  	ResourceContent
    37  	// ResourceSnapshot specifies a snapshot resource
    38  	ResourceSnapshot
    39  	// ResourceContainer specifies a container resource
    40  	ResourceContainer
    41  	// ResourceTask specifies a task resource
    42  	ResourceTask
    43  	// ResourceLease specifies a lease
    44  	ResourceLease
    45  	// ResourceIngest specifies a content ingest
    46  	ResourceIngest
    47  )
    48  
    49  const (
    50  	resourceContentFlat  = ResourceContent | 0x20
    51  	resourceSnapshotFlat = ResourceSnapshot | 0x20
    52  )
    53  
    54  var (
    55  	labelGCRoot       = []byte("containerd.io/gc.root")
    56  	labelGCSnapRef    = []byte("containerd.io/gc.ref.snapshot.")
    57  	labelGCContentRef = []byte("containerd.io/gc.ref.content")
    58  	labelGCExpire     = []byte("containerd.io/gc.expire")
    59  	labelGCFlat       = []byte("containerd.io/gc.flat")
    60  )
    61  
    62  func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
    63  	v1bkt := tx.Bucket(bucketKeyVersion)
    64  	if v1bkt == nil {
    65  		return nil
    66  	}
    67  
    68  	expThreshold := time.Now()
    69  
    70  	// iterate through each namespace
    71  	v1c := v1bkt.Cursor()
    72  
    73  	// cerr indicates the scan did not successfully send all
    74  	// the roots. The scan does not need to be cancelled but
    75  	// must return error at the end.
    76  	var cerr error
    77  	fn := func(n gc.Node) {
    78  		select {
    79  		case nc <- n:
    80  		case <-ctx.Done():
    81  			cerr = ctx.Err()
    82  		}
    83  	}
    84  
    85  	for k, v := v1c.First(); k != nil; k, v = v1c.Next() {
    86  		if v != nil {
    87  			continue
    88  		}
    89  		nbkt := v1bkt.Bucket(k)
    90  		ns := string(k)
    91  
    92  		lbkt := nbkt.Bucket(bucketKeyObjectLeases)
    93  		if lbkt != nil {
    94  			if err := lbkt.ForEach(func(k, v []byte) error {
    95  				if v != nil {
    96  					return nil
    97  				}
    98  				libkt := lbkt.Bucket(k)
    99  				var flat bool
   100  
   101  				if lblbkt := libkt.Bucket(bucketKeyObjectLabels); lblbkt != nil {
   102  					if expV := lblbkt.Get(labelGCExpire); expV != nil {
   103  						exp, err := time.Parse(time.RFC3339, string(expV))
   104  						if err != nil {
   105  							// label not used, log and continue to use lease
   106  							log.G(ctx).WithError(err).WithField("lease", string(k)).Infof("ignoring invalid expiration value %q", string(expV))
   107  						} else if expThreshold.After(exp) {
   108  							// lease has expired, skip
   109  							return nil
   110  						}
   111  					}
   112  
   113  					if flatV := lblbkt.Get(labelGCFlat); flatV != nil {
   114  						flat = true
   115  					}
   116  				}
   117  
   118  				fn(gcnode(ResourceLease, ns, string(k)))
   119  
   120  				// Emit content and snapshots as roots instead of implementing
   121  				// in references. Since leases cannot be referenced there is
   122  				// no need to allow the lookup to be recursive, handling here
   123  				// therefore reduces the number of database seeks.
   124  
   125  				ctype := ResourceContent
   126  				if flat {
   127  					ctype = resourceContentFlat
   128  				}
   129  
   130  				cbkt := libkt.Bucket(bucketKeyObjectContent)
   131  				if cbkt != nil {
   132  					if err := cbkt.ForEach(func(k, v []byte) error {
   133  						fn(gcnode(ctype, ns, string(k)))
   134  						return nil
   135  					}); err != nil {
   136  						return err
   137  					}
   138  				}
   139  
   140  				stype := ResourceSnapshot
   141  				if flat {
   142  					stype = resourceSnapshotFlat
   143  				}
   144  
   145  				sbkt := libkt.Bucket(bucketKeyObjectSnapshots)
   146  				if sbkt != nil {
   147  					if err := sbkt.ForEach(func(sk, sv []byte) error {
   148  						if sv != nil {
   149  							return nil
   150  						}
   151  						snbkt := sbkt.Bucket(sk)
   152  
   153  						return snbkt.ForEach(func(k, v []byte) error {
   154  							fn(gcnode(stype, ns, fmt.Sprintf("%s/%s", sk, k)))
   155  							return nil
   156  						})
   157  					}); err != nil {
   158  						return err
   159  					}
   160  				}
   161  
   162  				ibkt := libkt.Bucket(bucketKeyObjectIngests)
   163  				if ibkt != nil {
   164  					if err := ibkt.ForEach(func(k, v []byte) error {
   165  						fn(gcnode(ResourceIngest, ns, string(k)))
   166  						return nil
   167  					}); err != nil {
   168  						return err
   169  					}
   170  				}
   171  
   172  				return nil
   173  			}); err != nil {
   174  				return err
   175  			}
   176  		}
   177  
   178  		ibkt := nbkt.Bucket(bucketKeyObjectImages)
   179  		if ibkt != nil {
   180  			if err := ibkt.ForEach(func(k, v []byte) error {
   181  				if v != nil {
   182  					return nil
   183  				}
   184  
   185  				target := ibkt.Bucket(k).Bucket(bucketKeyTarget)
   186  				if target != nil {
   187  					contentKey := string(target.Get(bucketKeyDigest))
   188  					fn(gcnode(ResourceContent, ns, contentKey))
   189  				}
   190  				return sendLabelRefs(ns, ibkt.Bucket(k), fn)
   191  			}); err != nil {
   192  				return err
   193  			}
   194  		}
   195  
   196  		cbkt := nbkt.Bucket(bucketKeyObjectContent)
   197  		if cbkt != nil {
   198  			ibkt := cbkt.Bucket(bucketKeyObjectIngests)
   199  			if ibkt != nil {
   200  				if err := ibkt.ForEach(func(k, v []byte) error {
   201  					if v != nil {
   202  						return nil
   203  					}
   204  					ea, err := readExpireAt(ibkt.Bucket(k))
   205  					if err != nil {
   206  						return err
   207  					}
   208  					if ea == nil || expThreshold.After(*ea) {
   209  						return nil
   210  					}
   211  					fn(gcnode(ResourceIngest, ns, string(k)))
   212  					return nil
   213  				}); err != nil {
   214  					return err
   215  				}
   216  			}
   217  			cbkt = cbkt.Bucket(bucketKeyObjectBlob)
   218  			if cbkt != nil {
   219  				if err := cbkt.ForEach(func(k, v []byte) error {
   220  					if v != nil {
   221  						return nil
   222  					}
   223  
   224  					if isRootRef(cbkt.Bucket(k)) {
   225  						fn(gcnode(ResourceContent, ns, string(k)))
   226  					}
   227  
   228  					return nil
   229  				}); err != nil {
   230  					return err
   231  				}
   232  			}
   233  		}
   234  
   235  		cbkt = nbkt.Bucket(bucketKeyObjectContainers)
   236  		if cbkt != nil {
   237  			if err := cbkt.ForEach(func(k, v []byte) error {
   238  				if v != nil {
   239  					return nil
   240  				}
   241  
   242  				cibkt := cbkt.Bucket(k)
   243  				snapshotter := string(cibkt.Get(bucketKeySnapshotter))
   244  				if snapshotter != "" {
   245  					ss := string(cibkt.Get(bucketKeySnapshotKey))
   246  					fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, ss)))
   247  				}
   248  
   249  				return sendLabelRefs(ns, cibkt, fn)
   250  			}); err != nil {
   251  				return err
   252  			}
   253  		}
   254  
   255  		sbkt := nbkt.Bucket(bucketKeyObjectSnapshots)
   256  		if sbkt != nil {
   257  			if err := sbkt.ForEach(func(sk, sv []byte) error {
   258  				if sv != nil {
   259  					return nil
   260  				}
   261  				snbkt := sbkt.Bucket(sk)
   262  
   263  				return snbkt.ForEach(func(k, v []byte) error {
   264  					if v != nil {
   265  						return nil
   266  					}
   267  					if isRootRef(snbkt.Bucket(k)) {
   268  						fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)))
   269  					}
   270  					return nil
   271  				})
   272  			}); err != nil {
   273  				return err
   274  			}
   275  		}
   276  	}
   277  	return cerr
   278  }
   279  
   280  func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)) error {
   281  	switch node.Type {
   282  	case ResourceContent:
   283  		bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectBlob, []byte(node.Key))
   284  		if bkt == nil {
   285  			// Node may be created from dead edge
   286  			return nil
   287  		}
   288  
   289  		return sendLabelRefs(node.Namespace, bkt, fn)
   290  	case ResourceSnapshot, resourceSnapshotFlat:
   291  		parts := strings.SplitN(node.Key, "/", 2)
   292  		if len(parts) != 2 {
   293  			return errors.Errorf("invalid snapshot gc key %s", node.Key)
   294  		}
   295  		ss := parts[0]
   296  		name := parts[1]
   297  
   298  		bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectSnapshots, []byte(ss), []byte(name))
   299  		if bkt == nil {
   300  			// Node may be created from dead edge
   301  			return nil
   302  		}
   303  
   304  		if pv := bkt.Get(bucketKeyParent); len(pv) > 0 {
   305  			fn(gcnode(node.Type, node.Namespace, fmt.Sprintf("%s/%s", ss, pv)))
   306  		}
   307  
   308  		// Do not send labeled references for flat snapshot refs
   309  		if node.Type == resourceSnapshotFlat {
   310  			return nil
   311  		}
   312  
   313  		return sendLabelRefs(node.Namespace, bkt, fn)
   314  	case ResourceIngest:
   315  		// Send expected value
   316  		bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(node.Key))
   317  		if bkt == nil {
   318  			// Node may be created from dead edge
   319  			return nil
   320  		}
   321  		// Load expected
   322  		expected := bkt.Get(bucketKeyExpected)
   323  		if len(expected) > 0 {
   324  			fn(gcnode(ResourceContent, node.Namespace, string(expected)))
   325  		}
   326  		return nil
   327  	}
   328  
   329  	return nil
   330  }
   331  
   332  func scanAll(ctx context.Context, tx *bolt.Tx, fn func(ctx context.Context, n gc.Node) error) error {
   333  	v1bkt := tx.Bucket(bucketKeyVersion)
   334  	if v1bkt == nil {
   335  		return nil
   336  	}
   337  
   338  	// iterate through each namespace
   339  	v1c := v1bkt.Cursor()
   340  
   341  	for k, v := v1c.First(); k != nil; k, v = v1c.Next() {
   342  		if v != nil {
   343  			continue
   344  		}
   345  		nbkt := v1bkt.Bucket(k)
   346  		ns := string(k)
   347  
   348  		lbkt := nbkt.Bucket(bucketKeyObjectLeases)
   349  		if lbkt != nil {
   350  			if err := lbkt.ForEach(func(k, v []byte) error {
   351  				if v != nil {
   352  					return nil
   353  				}
   354  				return fn(ctx, gcnode(ResourceLease, ns, string(k)))
   355  			}); err != nil {
   356  				return err
   357  			}
   358  		}
   359  
   360  		sbkt := nbkt.Bucket(bucketKeyObjectSnapshots)
   361  		if sbkt != nil {
   362  			if err := sbkt.ForEach(func(sk, sv []byte) error {
   363  				if sv != nil {
   364  					return nil
   365  				}
   366  				snbkt := sbkt.Bucket(sk)
   367  				return snbkt.ForEach(func(k, v []byte) error {
   368  					if v != nil {
   369  						return nil
   370  					}
   371  					node := gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k))
   372  					return fn(ctx, node)
   373  				})
   374  			}); err != nil {
   375  				return err
   376  			}
   377  		}
   378  
   379  		cbkt := nbkt.Bucket(bucketKeyObjectContent)
   380  		if cbkt != nil {
   381  			ibkt := cbkt.Bucket(bucketKeyObjectIngests)
   382  			if ibkt != nil {
   383  				if err := ibkt.ForEach(func(k, v []byte) error {
   384  					if v != nil {
   385  						return nil
   386  					}
   387  					node := gcnode(ResourceIngest, ns, string(k))
   388  					return fn(ctx, node)
   389  				}); err != nil {
   390  					return err
   391  				}
   392  			}
   393  
   394  			cbkt = cbkt.Bucket(bucketKeyObjectBlob)
   395  			if cbkt != nil {
   396  				if err := cbkt.ForEach(func(k, v []byte) error {
   397  					if v != nil {
   398  						return nil
   399  					}
   400  					node := gcnode(ResourceContent, ns, string(k))
   401  					return fn(ctx, node)
   402  				}); err != nil {
   403  					return err
   404  				}
   405  			}
   406  		}
   407  	}
   408  
   409  	return nil
   410  }
   411  
   412  func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error {
   413  	v1bkt := tx.Bucket(bucketKeyVersion)
   414  	if v1bkt == nil {
   415  		return nil
   416  	}
   417  
   418  	nsbkt := v1bkt.Bucket([]byte(node.Namespace))
   419  	if nsbkt == nil {
   420  		return nil
   421  	}
   422  
   423  	switch node.Type {
   424  	case ResourceContent:
   425  		cbkt := nsbkt.Bucket(bucketKeyObjectContent)
   426  		if cbkt != nil {
   427  			cbkt = cbkt.Bucket(bucketKeyObjectBlob)
   428  		}
   429  		if cbkt != nil {
   430  			log.G(ctx).WithField("key", node.Key).Debug("remove content")
   431  			return cbkt.DeleteBucket([]byte(node.Key))
   432  		}
   433  	case ResourceSnapshot:
   434  		sbkt := nsbkt.Bucket(bucketKeyObjectSnapshots)
   435  		if sbkt != nil {
   436  			parts := strings.SplitN(node.Key, "/", 2)
   437  			if len(parts) != 2 {
   438  				return errors.Errorf("invalid snapshot gc key %s", node.Key)
   439  			}
   440  			ssbkt := sbkt.Bucket([]byte(parts[0]))
   441  			if ssbkt != nil {
   442  				log.G(ctx).WithField("key", parts[1]).WithField("snapshotter", parts[0]).Debug("remove snapshot")
   443  				return ssbkt.DeleteBucket([]byte(parts[1]))
   444  			}
   445  		}
   446  	case ResourceLease:
   447  		lbkt := nsbkt.Bucket(bucketKeyObjectLeases)
   448  		if lbkt != nil {
   449  			return lbkt.DeleteBucket([]byte(node.Key))
   450  		}
   451  	case ResourceIngest:
   452  		ibkt := nsbkt.Bucket(bucketKeyObjectContent)
   453  		if ibkt != nil {
   454  			ibkt = ibkt.Bucket(bucketKeyObjectIngests)
   455  		}
   456  		if ibkt != nil {
   457  			log.G(ctx).WithField("ref", node.Key).Debug("remove ingest")
   458  			return ibkt.DeleteBucket([]byte(node.Key))
   459  		}
   460  	}
   461  
   462  	return nil
   463  }
   464  
   465  // sendLabelRefs sends all snapshot and content references referred to by the labels in the bkt
   466  func sendLabelRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error {
   467  	lbkt := bkt.Bucket(bucketKeyObjectLabels)
   468  	if lbkt != nil {
   469  		lc := lbkt.Cursor()
   470  
   471  		labelRef := string(labelGCContentRef)
   472  		for k, v := lc.Seek(labelGCContentRef); k != nil && strings.HasPrefix(string(k), labelRef); k, v = lc.Next() {
   473  			if ks := string(k); ks != labelRef {
   474  				// Allow reference naming separated by . or /, ignore names
   475  				if ks[len(labelRef)] != '.' && ks[len(labelRef)] != '/' {
   476  					continue
   477  				}
   478  			}
   479  
   480  			fn(gcnode(ResourceContent, ns, string(v)))
   481  		}
   482  
   483  		for k, v := lc.Seek(labelGCSnapRef); k != nil && strings.HasPrefix(string(k), string(labelGCSnapRef)); k, v = lc.Next() {
   484  			snapshotter := k[len(labelGCSnapRef):]
   485  			if i := bytes.IndexByte(snapshotter, '/'); i >= 0 {
   486  				snapshotter = snapshotter[:i]
   487  			}
   488  			fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v)))
   489  		}
   490  
   491  	}
   492  	return nil
   493  }
   494  
   495  func isRootRef(bkt *bolt.Bucket) bool {
   496  	lbkt := bkt.Bucket(bucketKeyObjectLabels)
   497  	if lbkt != nil {
   498  		rv := lbkt.Get(labelGCRoot)
   499  		if rv != nil {
   500  			// TODO: interpret rv as a timestamp and skip if expired
   501  			return true
   502  		}
   503  	}
   504  	return false
   505  }
   506  
   507  func gcnode(t gc.ResourceType, ns, key string) gc.Node {
   508  	return gc.Node{
   509  		Type:      t,
   510  		Namespace: ns,
   511  		Key:       key,
   512  	}
   513  }