github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/snapshots/windows/windows.go (about)

     1  // +build windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package windows
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"os"
    25  	"path/filepath"
    26  	"strconv"
    27  	"strings"
    28  
    29  	winfs "github.com/Microsoft/go-winio/pkg/fs"
    30  	"github.com/Microsoft/go-winio/vhd"
    31  	"github.com/Microsoft/hcsshim"
    32  	"github.com/containerd/containerd/errdefs"
    33  	"github.com/containerd/containerd/log"
    34  	"github.com/containerd/containerd/mount"
    35  	"github.com/containerd/containerd/plugin"
    36  	"github.com/containerd/containerd/snapshots"
    37  	"github.com/containerd/containerd/snapshots/storage"
    38  	"github.com/containerd/continuity/fs"
    39  	"github.com/pkg/errors"
    40  )
    41  
    42  func init() {
    43  	plugin.Register(&plugin.Registration{
    44  		Type: plugin.SnapshotPlugin,
    45  		ID:   "windows",
    46  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    47  			return NewSnapshotter(ic.Root)
    48  		},
    49  	})
    50  }
    51  
    52  const (
    53  	rootfsSizeLabel = "containerd.io/snapshot/io.microsoft.container.storage.rootfs.size-gb"
    54  )
    55  
    56  type snapshotter struct {
    57  	root string
    58  	info hcsshim.DriverInfo
    59  	ms   *storage.MetaStore
    60  }
    61  
    62  // NewSnapshotter returns a new windows snapshotter
    63  func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
    64  	fsType, err := winfs.GetFileSystemType(root)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	if strings.ToLower(fsType) != "ntfs" {
    69  		return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root)
    70  	}
    71  
    72  	if err := os.MkdirAll(root, 0700); err != nil {
    73  		return nil, err
    74  	}
    75  	ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
    81  		return nil, err
    82  	}
    83  
    84  	return &snapshotter{
    85  		info: hcsshim.DriverInfo{
    86  			HomeDir: filepath.Join(root, "snapshots"),
    87  		},
    88  		root: root,
    89  		ms:   ms,
    90  	}, nil
    91  }
    92  
    93  // Stat returns the info for an active or committed snapshot by name or
    94  // key.
    95  //
    96  // Should be used for parent resolution, existence checks and to discern
    97  // the kind of snapshot.
    98  func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
    99  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   100  	if err != nil {
   101  		return snapshots.Info{}, err
   102  	}
   103  	defer t.Rollback()
   104  
   105  	_, info, _, err := storage.GetInfo(ctx, key)
   106  	return info, err
   107  }
   108  
   109  func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
   110  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   111  	if err != nil {
   112  		return snapshots.Info{}, err
   113  	}
   114  	defer t.Rollback()
   115  
   116  	info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
   117  	if err != nil {
   118  		return snapshots.Info{}, err
   119  	}
   120  
   121  	if err := t.Commit(); err != nil {
   122  		return snapshots.Info{}, err
   123  	}
   124  
   125  	return info, nil
   126  }
   127  
   128  func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
   129  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   130  	if err != nil {
   131  		return snapshots.Usage{}, err
   132  	}
   133  	id, info, usage, err := storage.GetInfo(ctx, key)
   134  	t.Rollback() // transaction no longer needed at this point.
   135  
   136  	if err != nil {
   137  		return snapshots.Usage{}, err
   138  	}
   139  
   140  	if info.Kind == snapshots.KindActive {
   141  		path := s.getSnapshotDir(id)
   142  		du, err := fs.DiskUsage(ctx, path)
   143  		if err != nil {
   144  			return snapshots.Usage{}, err
   145  		}
   146  
   147  		usage = snapshots.Usage(du)
   148  	}
   149  
   150  	return usage, nil
   151  }
   152  
   153  func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   154  	return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
   155  }
   156  
   157  func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   158  	return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
   159  }
   160  
   161  // Mounts returns the mounts for the transaction identified by key. Can be
   162  // called on an read-write or readonly transaction.
   163  //
   164  // This can be used to recover mounts after calling View or Prepare.
   165  func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
   166  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	defer t.Rollback()
   171  
   172  	snapshot, err := storage.GetSnapshot(ctx, key)
   173  	if err != nil {
   174  		return nil, errors.Wrap(err, "failed to get snapshot mount")
   175  	}
   176  	return s.mounts(snapshot), nil
   177  }
   178  
   179  func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
   180  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   181  	if err != nil {
   182  		return err
   183  	}
   184  
   185  	defer func() {
   186  		if err != nil {
   187  			if rerr := t.Rollback(); rerr != nil {
   188  				log.G(ctx).WithError(rerr).Warn("failed to rollback transaction")
   189  			}
   190  		}
   191  	}()
   192  
   193  	// grab the existing id
   194  	id, _, _, err := storage.GetInfo(ctx, key)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	usage, err := fs.DiskUsage(ctx, s.getSnapshotDir(id))
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
   205  		return errors.Wrap(err, "failed to commit snapshot")
   206  	}
   207  	return t.Commit()
   208  }
   209  
   210  // Remove abandons the transaction identified by key. All resources
   211  // associated with the key will be removed.
   212  func (s *snapshotter) Remove(ctx context.Context, key string) error {
   213  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	defer t.Rollback()
   218  
   219  	id, _, err := storage.Remove(ctx, key)
   220  	if err != nil {
   221  		return errors.Wrap(err, "failed to remove")
   222  	}
   223  
   224  	path := s.getSnapshotDir(id)
   225  	renamedID := "rm-" + id
   226  	renamed := s.getSnapshotDir(renamedID)
   227  	if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) {
   228  		if !os.IsPermission(err) {
   229  			return err
   230  		}
   231  		// If permission denied, it's possible that the scratch is still mounted, an
   232  		// artifact after a hard daemon crash for example. Worth a shot to try detaching it
   233  		// before retrying the rename.
   234  		if detachErr := vhd.DetachVhd(filepath.Join(path, "sandbox.vhdx")); detachErr != nil {
   235  			return errors.Wrapf(err, "failed to detach VHD: %s", detachErr)
   236  		}
   237  		if renameErr := os.Rename(path, renamed); renameErr != nil && !os.IsNotExist(renameErr) {
   238  			return errors.Wrapf(err, "second rename attempt following detach failed: %s", renameErr)
   239  		}
   240  	}
   241  
   242  	if err := t.Commit(); err != nil {
   243  		if err1 := os.Rename(renamed, path); err1 != nil {
   244  			// May cause inconsistent data on disk
   245  			log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit")
   246  		}
   247  		return errors.Wrap(err, "failed to commit")
   248  	}
   249  
   250  	if err := hcsshim.DestroyLayer(s.info, renamedID); err != nil {
   251  		// Must be cleaned up, any "rm-*" could be removed if no active transactions
   252  		log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem")
   253  	}
   254  
   255  	return nil
   256  }
   257  
   258  // Walk the committed snapshots.
   259  func (s *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error {
   260  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   261  	if err != nil {
   262  		return err
   263  	}
   264  	defer t.Rollback()
   265  
   266  	return storage.WalkInfo(ctx, fn, fs...)
   267  }
   268  
   269  // Close closes the snapshotter
   270  func (s *snapshotter) Close() error {
   271  	return s.ms.Close()
   272  }
   273  
   274  func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
   275  	var (
   276  		roFlag           string
   277  		source           string
   278  		parentLayerPaths []string
   279  	)
   280  
   281  	if sn.Kind == snapshots.KindView {
   282  		roFlag = "ro"
   283  	} else {
   284  		roFlag = "rw"
   285  	}
   286  
   287  	if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive {
   288  		source = s.getSnapshotDir(sn.ID)
   289  		parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs)
   290  	} else {
   291  		source = s.getSnapshotDir(sn.ParentIDs[0])
   292  		parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:])
   293  	}
   294  
   295  	// error is not checked here, as a string array will never fail to Marshal
   296  	parentLayersJSON, _ := json.Marshal(parentLayerPaths)
   297  	parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON)
   298  
   299  	var mounts []mount.Mount
   300  	mounts = append(mounts, mount.Mount{
   301  		Source: source,
   302  		Type:   "windows-layer",
   303  		Options: []string{
   304  			roFlag,
   305  			parentLayersOption,
   306  		},
   307  	})
   308  
   309  	return mounts
   310  }
   311  
   312  func (s *snapshotter) getSnapshotDir(id string) string {
   313  	return filepath.Join(s.root, "snapshots", id)
   314  }
   315  
   316  func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
   317  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	defer t.Rollback()
   322  
   323  	newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
   324  	if err != nil {
   325  		return nil, errors.Wrap(err, "failed to create snapshot")
   326  	}
   327  
   328  	if kind == snapshots.KindActive {
   329  		parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs)
   330  
   331  		var parentPath string
   332  		if len(parentLayerPaths) != 0 {
   333  			parentPath = parentLayerPaths[0]
   334  		}
   335  
   336  		if err := hcsshim.CreateSandboxLayer(s.info, newSnapshot.ID, parentPath, parentLayerPaths); err != nil {
   337  			return nil, errors.Wrap(err, "failed to create sandbox layer")
   338  		}
   339  
   340  		var snapshotInfo snapshots.Info
   341  		for _, o := range opts {
   342  			o(&snapshotInfo)
   343  		}
   344  
   345  		var sizeGB int
   346  		if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeLabel]; ok {
   347  			i32, err := strconv.ParseInt(sizeGBstr, 10, 32)
   348  			if err != nil {
   349  				return nil, errors.Wrapf(err, "failed to parse annotation %q=%q", rootfsSizeLabel, sizeGBstr)
   350  			}
   351  			sizeGB = int(i32)
   352  		}
   353  
   354  		if sizeGB > 0 {
   355  			const gbToByte = 1024 * 1024 * 1024
   356  			if err := hcsshim.ExpandSandboxSize(s.info, newSnapshot.ID, uint64(gbToByte*sizeGB)); err != nil {
   357  				return nil, errors.Wrapf(err, "failed to expand scratch size to %d GB", sizeGB)
   358  			}
   359  		}
   360  	}
   361  
   362  	if err := t.Commit(); err != nil {
   363  		return nil, errors.Wrap(err, "commit failed")
   364  	}
   365  
   366  	return s.mounts(newSnapshot), nil
   367  }
   368  
   369  func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
   370  	var parentLayerPaths []string
   371  	for _, ID := range parentIDs {
   372  		parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID))
   373  	}
   374  	return parentLayerPaths
   375  }