github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/snapshots/snapshotter.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 snapshots
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/containerd/containerd/mount"
    26  )
    27  
    28  const (
    29  	inheritedLabelsPrefix = "containerd.io/snapshot/"
    30  	labelSnapshotRef      = "containerd.io/snapshot.ref"
    31  )
    32  
    33  // Kind identifies the kind of snapshot.
    34  type Kind uint8
    35  
    36  // definitions of snapshot kinds
    37  const (
    38  	KindUnknown Kind = iota
    39  	KindView
    40  	KindActive
    41  	KindCommitted
    42  )
    43  
    44  // ParseKind parses the provided string into a Kind
    45  //
    46  // If the string cannot be parsed KindUnknown is returned
    47  func ParseKind(s string) Kind {
    48  	s = strings.ToLower(s)
    49  	switch s {
    50  	case "view":
    51  		return KindView
    52  	case "active":
    53  		return KindActive
    54  	case "committed":
    55  		return KindCommitted
    56  	}
    57  
    58  	return KindUnknown
    59  }
    60  
    61  // String returns the string representation of the Kind
    62  func (k Kind) String() string {
    63  	switch k {
    64  	case KindView:
    65  		return "View"
    66  	case KindActive:
    67  		return "Active"
    68  	case KindCommitted:
    69  		return "Committed"
    70  	}
    71  
    72  	return "Unknown"
    73  }
    74  
    75  // MarshalJSON the Kind to JSON
    76  func (k Kind) MarshalJSON() ([]byte, error) {
    77  	return json.Marshal(k.String())
    78  }
    79  
    80  // UnmarshalJSON the Kind from JSON
    81  func (k *Kind) UnmarshalJSON(b []byte) error {
    82  	var s string
    83  	if err := json.Unmarshal(b, &s); err != nil {
    84  		return err
    85  	}
    86  
    87  	*k = ParseKind(s)
    88  	return nil
    89  }
    90  
    91  // Info provides information about a particular snapshot.
    92  // JSON marshallability is supported for interactive with tools like ctr,
    93  type Info struct {
    94  	Kind   Kind   // active or committed snapshot
    95  	Name   string // name or key of snapshot
    96  	Parent string `json:",omitempty"` // name of parent snapshot
    97  
    98  	// Labels for a snapshot.
    99  	//
   100  	// Note: only labels prefixed with `containerd.io/snapshot/` will be inherited by the
   101  	// snapshotter's `Prepare`, `View`, or `Commit` calls.
   102  	Labels  map[string]string `json:",omitempty"`
   103  	Created time.Time         `json:",omitempty"` // Created time
   104  	Updated time.Time         `json:",omitempty"` // Last update time
   105  }
   106  
   107  // Usage defines statistics for disk resources consumed by the snapshot.
   108  //
   109  // These resources only include the resources consumed by the snapshot itself
   110  // and does not include resources usage by the parent.
   111  type Usage struct {
   112  	Inodes int64 // number of inodes in use.
   113  	Size   int64 // provides usage, in bytes, of snapshot
   114  }
   115  
   116  // Add the provided usage to the current usage
   117  func (u *Usage) Add(other Usage) {
   118  	u.Size += other.Size
   119  
   120  	// TODO(stevvooe): assumes independent inodes, but provides and upper
   121  	// bound. This should be pretty close, assuming the inodes for a
   122  	// snapshot are roughly unique to it. Don't trust this assumption.
   123  	u.Inodes += other.Inodes
   124  }
   125  
   126  // WalkFunc defines the callback for a snapshot walk.
   127  type WalkFunc func(context.Context, Info) error
   128  
   129  // Snapshotter defines the methods required to implement a snapshot snapshotter for
   130  // allocating, snapshotting and mounting filesystem changesets. The model works
   131  // by building up sets of changes with parent-child relationships.
   132  //
   133  // A snapshot represents a filesystem state. Every snapshot has a parent, where
   134  // the empty parent is represented by the empty string. A diff can be taken
   135  // between a parent and its snapshot to generate a classic layer.
   136  //
   137  // An active snapshot is created by calling `Prepare`. After mounting, changes
   138  // can be made to the snapshot. The act of committing creates a committed
   139  // snapshot. The committed snapshot will get the parent of active snapshot. The
   140  // committed snapshot can then be used as a parent. Active snapshots can never
   141  // act as a parent.
   142  //
   143  // Snapshots are best understood by their lifecycle. Active snapshots are
   144  // always created with Prepare or View. Committed snapshots are always created
   145  // with Commit.  Active snapshots never become committed snapshots and vice
   146  // versa. All snapshots may be removed.
   147  //
   148  // For consistency, we define the following terms to be used throughout this
   149  // interface for snapshotter implementations:
   150  //
   151  // 	`ctx` - refers to a context.Context
   152  // 	`key` - refers to an active snapshot
   153  // 	`name` - refers to a committed snapshot
   154  // 	`parent` - refers to the parent in relation
   155  //
   156  // Most methods take various combinations of these identifiers. Typically,
   157  // `name` and `parent` will be used in cases where a method *only* takes
   158  // committed snapshots. `key` will be used to refer to active snapshots in most
   159  // cases, except where noted. All variables used to access snapshots use the
   160  // same key space. For example, an active snapshot may not share the same key
   161  // with a committed snapshot.
   162  //
   163  // We cover several examples below to demonstrate the utility of a snapshot
   164  // snapshotter.
   165  //
   166  // Importing a Layer
   167  //
   168  // To import a layer, we simply have the Snapshotter provide a list of
   169  // mounts to be applied such that our dst will capture a changeset. We start
   170  // out by getting a path to the layer tar file and creating a temp location to
   171  // unpack it to:
   172  //
   173  //	layerPath, tmpDir := getLayerPath(), mkTmpDir() // just a path to layer tar file.
   174  //
   175  // We start by using a Snapshotter to Prepare a new snapshot transaction, using a
   176  // key and descending from the empty parent "". To prevent our layer from being
   177  // garbage collected during unpacking, we add the `containerd.io/gc.root` label:
   178  //
   179  //	noGcOpt := snapshots.WithLabels(map[string]string{
   180  //		"containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339),
   181  //	})
   182  //	mounts, err := snapshotter.Prepare(ctx, key, "", noGcOpt)
   183  // 	if err != nil { ... }
   184  //
   185  // We get back a list of mounts from Snapshotter.Prepare, with the key identifying
   186  // the active snapshot. Mount this to the temporary location with the
   187  // following:
   188  //
   189  //	if err := mount.All(mounts, tmpDir); err != nil { ... }
   190  //
   191  // Once the mounts are performed, our temporary location is ready to capture
   192  // a diff. In practice, this works similar to a filesystem transaction. The
   193  // next step is to unpack the layer. We have a special function unpackLayer
   194  // that applies the contents of the layer to target location and calculates the
   195  // DiffID of the unpacked layer (this is a requirement for docker
   196  // implementation):
   197  //
   198  //	layer, err := os.Open(layerPath)
   199  //	if err != nil { ... }
   200  // 	digest, err := unpackLayer(tmpLocation, layer) // unpack into layer location
   201  // 	if err != nil { ... }
   202  //
   203  // When the above completes, we should have a filesystem the represents the
   204  // contents of the layer. Careful implementations should verify that digest
   205  // matches the expected DiffID. When completed, we unmount the mounts:
   206  //
   207  //	unmount(mounts) // optional, for now
   208  //
   209  // Now that we've verified and unpacked our layer, we commit the active
   210  // snapshot to a name. For this example, we are just going to use the layer
   211  // digest, but in practice, this will probably be the ChainID. This also removes
   212  // the active snapshot:
   213  //
   214  //	if err := snapshotter.Commit(ctx, digest.String(), key, noGcOpt); err != nil { ... }
   215  //
   216  // Now, we have a layer in the Snapshotter that can be accessed with the digest
   217  // provided during commit.
   218  //
   219  // Importing the Next Layer
   220  //
   221  // Making a layer depend on the above is identical to the process described
   222  // above except that the parent is provided as parent when calling
   223  // Manager.Prepare, assuming a clean, unique key identifier:
   224  //
   225  // 	mounts, err := snapshotter.Prepare(ctx, key, parentDigest, noGcOpt)
   226  //
   227  // We then mount, apply and commit, as we did above. The new snapshot will be
   228  // based on the content of the previous one.
   229  //
   230  // Running a Container
   231  //
   232  // To run a container, we simply provide Snapshotter.Prepare the committed image
   233  // snapshot as the parent. After mounting, the prepared path can
   234  // be used directly as the container's filesystem:
   235  //
   236  // 	mounts, err := snapshotter.Prepare(ctx, containerKey, imageRootFSChainID)
   237  //
   238  // The returned mounts can then be passed directly to the container runtime. If
   239  // one would like to create a new image from the filesystem, Manager.Commit is
   240  // called:
   241  //
   242  // 	if err := snapshotter.Commit(ctx, newImageSnapshot, containerKey); err != nil { ... }
   243  //
   244  // Alternatively, for most container runs, Snapshotter.Remove will be called to
   245  // signal the Snapshotter to abandon the changes.
   246  type Snapshotter interface {
   247  	// Stat returns the info for an active or committed snapshot by name or
   248  	// key.
   249  	//
   250  	// Should be used for parent resolution, existence checks and to discern
   251  	// the kind of snapshot.
   252  	Stat(ctx context.Context, key string) (Info, error)
   253  
   254  	// Update updates the info for a snapshot.
   255  	//
   256  	// Only mutable properties of a snapshot may be updated.
   257  	Update(ctx context.Context, info Info, fieldpaths ...string) (Info, error)
   258  
   259  	// Usage returns the resource usage of an active or committed snapshot
   260  	// excluding the usage of parent snapshots.
   261  	//
   262  	// The running time of this call for active snapshots is dependent on
   263  	// implementation, but may be proportional to the size of the resource.
   264  	// Callers should take this into consideration. Implementations should
   265  	// attempt to honer context cancellation and avoid taking locks when making
   266  	// the calculation.
   267  	Usage(ctx context.Context, key string) (Usage, error)
   268  
   269  	// Mounts returns the mounts for the active snapshot transaction identified
   270  	// by key. Can be called on an read-write or readonly transaction. This is
   271  	// available only for active snapshots.
   272  	//
   273  	// This can be used to recover mounts after calling View or Prepare.
   274  	Mounts(ctx context.Context, key string) ([]mount.Mount, error)
   275  
   276  	// Prepare creates an active snapshot identified by key descending from the
   277  	// provided parent.  The returned mounts can be used to mount the snapshot
   278  	// to capture changes.
   279  	//
   280  	// If a parent is provided, after performing the mounts, the destination
   281  	// will start with the content of the parent. The parent must be a
   282  	// committed snapshot. Changes to the mounted destination will be captured
   283  	// in relation to the parent. The default parent, "", is an empty
   284  	// directory.
   285  	//
   286  	// The changes may be saved to a committed snapshot by calling Commit. When
   287  	// one is done with the transaction, Remove should be called on the key.
   288  	//
   289  	// Multiple calls to Prepare or View with the same key should fail.
   290  	Prepare(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error)
   291  
   292  	// View behaves identically to Prepare except the result may not be
   293  	// committed back to the snapshot snapshotter. View returns a readonly view on
   294  	// the parent, with the active snapshot being tracked by the given key.
   295  	//
   296  	// This method operates identically to Prepare, except that Mounts returned
   297  	// may have the readonly flag set. Any modifications to the underlying
   298  	// filesystem will be ignored. Implementations may perform this in a more
   299  	// efficient manner that differs from what would be attempted with
   300  	// `Prepare`.
   301  	//
   302  	// Commit may not be called on the provided key and will return an error.
   303  	// To collect the resources associated with key, Remove must be called with
   304  	// key as the argument.
   305  	View(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error)
   306  
   307  	// Commit captures the changes between key and its parent into a snapshot
   308  	// identified by name.  The name can then be used with the snapshotter's other
   309  	// methods to create subsequent snapshots.
   310  	//
   311  	// A committed snapshot will be created under name with the parent of the
   312  	// active snapshot.
   313  	//
   314  	// After commit, the snapshot identified by key is removed.
   315  	Commit(ctx context.Context, name, key string, opts ...Opt) error
   316  
   317  	// Remove the committed or active snapshot by the provided key.
   318  	//
   319  	// All resources associated with the key will be removed.
   320  	//
   321  	// If the snapshot is a parent of another snapshot, its children must be
   322  	// removed before proceeding.
   323  	Remove(ctx context.Context, key string) error
   324  
   325  	// Walk will call the provided function for each snapshot in the
   326  	// snapshotter which match the provided filters. If no filters are
   327  	// given all items will be walked.
   328  	// Filters:
   329  	//  name
   330  	//  parent
   331  	//  kind (active,view,committed)
   332  	//  labels.(label)
   333  	Walk(ctx context.Context, fn WalkFunc, filters ...string) error
   334  
   335  	// Close releases the internal resources.
   336  	//
   337  	// Close is expected to be called on the end of the lifecycle of the snapshotter,
   338  	// but not mandatory.
   339  	//
   340  	// Close returns nil when it is already closed.
   341  	Close() error
   342  }
   343  
   344  // Cleaner defines a type capable of performing asynchronous resource cleanup.
   345  // The Cleaner interface should be used by snapshotters which implement fast
   346  // removal and deferred resource cleanup. This prevents snapshots from needing
   347  // to perform lengthy resource cleanup before acknowledging a snapshot key
   348  // has been removed and available for re-use. This is also useful when
   349  // performing multi-key removal with the intent of cleaning up all the
   350  // resources after each snapshot key has been removed.
   351  type Cleaner interface {
   352  	Cleanup(ctx context.Context) error
   353  }
   354  
   355  // Opt allows setting mutable snapshot properties on creation
   356  type Opt func(info *Info) error
   357  
   358  // WithLabels appends labels to a created snapshot
   359  func WithLabels(labels map[string]string) Opt {
   360  	return func(info *Info) error {
   361  		if info.Labels == nil {
   362  			info.Labels = make(map[string]string)
   363  		}
   364  
   365  		for k, v := range labels {
   366  			info.Labels[k] = v
   367  		}
   368  
   369  		return nil
   370  	}
   371  }
   372  
   373  // FilterInheritedLabels filters the provided labels by removing any key which
   374  // isn't a snapshot label. Snapshot labels have a prefix of "containerd.io/snapshot/"
   375  // or are the "containerd.io/snapshot.ref" label.
   376  func FilterInheritedLabels(labels map[string]string) map[string]string {
   377  	if labels == nil {
   378  		return nil
   379  	}
   380  
   381  	filtered := make(map[string]string)
   382  	for k, v := range labels {
   383  		if k == labelSnapshotRef || strings.HasPrefix(k, inheritedLabelsPrefix) {
   384  			filtered[k] = v
   385  		}
   386  	}
   387  	return filtered
   388  }