github.com/demonoid81/containerd@v1.3.4/snapshots/lcow/lcow.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 lcow
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"os"
    27  	"path/filepath"
    28  	"strconv"
    29  	"strings"
    30  	"sync"
    31  	"time"
    32  
    33  	winfs "github.com/Microsoft/go-winio/pkg/fs"
    34  	"github.com/Microsoft/hcsshim/pkg/go-runhcs"
    35  	"github.com/containerd/containerd/errdefs"
    36  	"github.com/containerd/containerd/log"
    37  	"github.com/containerd/containerd/mount"
    38  	"github.com/containerd/containerd/plugin"
    39  	"github.com/containerd/containerd/snapshots"
    40  	"github.com/containerd/containerd/snapshots/storage"
    41  	"github.com/containerd/continuity/fs"
    42  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    43  	"github.com/pkg/errors"
    44  )
    45  
    46  func init() {
    47  	plugin.Register(&plugin.Registration{
    48  		Type: plugin.SnapshotPlugin,
    49  		ID:   "windows-lcow",
    50  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    51  			ic.Meta.Platforms = append(ic.Meta.Platforms, ocispec.Platform{
    52  				OS:           "linux",
    53  				Architecture: "amd64",
    54  			})
    55  			return NewSnapshotter(ic.Root)
    56  		},
    57  	})
    58  }
    59  
    60  type snapshotter struct {
    61  	root string
    62  	ms   *storage.MetaStore
    63  
    64  	scratchLock sync.Mutex
    65  }
    66  
    67  // NewSnapshotter returns a new windows snapshotter
    68  func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
    69  	fsType, err := winfs.GetFileSystemType(root)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	if strings.ToLower(fsType) != "ntfs" {
    74  		return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root)
    75  	}
    76  
    77  	if err := os.MkdirAll(root, 0700); err != nil {
    78  		return nil, err
    79  	}
    80  	ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
    86  		return nil, err
    87  	}
    88  
    89  	return &snapshotter{
    90  		root: root,
    91  		ms:   ms,
    92  	}, nil
    93  }
    94  
    95  // Stat returns the info for an active or committed snapshot by name or
    96  // key.
    97  //
    98  // Should be used for parent resolution, existence checks and to discern
    99  // the kind of snapshot.
   100  func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
   101  	log.G(ctx).Debug("Starting Stat")
   102  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   103  	if err != nil {
   104  		return snapshots.Info{}, err
   105  	}
   106  	defer t.Rollback()
   107  
   108  	_, info, _, err := storage.GetInfo(ctx, key)
   109  	return info, err
   110  }
   111  
   112  func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
   113  	log.G(ctx).Debug("Starting Update")
   114  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   115  	if err != nil {
   116  		return snapshots.Info{}, err
   117  	}
   118  	defer t.Rollback()
   119  
   120  	info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
   121  	if err != nil {
   122  		return snapshots.Info{}, err
   123  	}
   124  
   125  	if err := t.Commit(); err != nil {
   126  		return snapshots.Info{}, err
   127  	}
   128  
   129  	return info, nil
   130  }
   131  
   132  func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
   133  	log.G(ctx).Debug("Starting Usage")
   134  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   135  	if err != nil {
   136  		return snapshots.Usage{}, err
   137  	}
   138  	defer t.Rollback()
   139  
   140  	_, info, usage, err := storage.GetInfo(ctx, key)
   141  	if err != nil {
   142  		return snapshots.Usage{}, err
   143  	}
   144  
   145  	if info.Kind == snapshots.KindActive {
   146  		du := fs.Usage{
   147  			Size: 0,
   148  		}
   149  		usage = snapshots.Usage(du)
   150  	}
   151  
   152  	return usage, nil
   153  }
   154  
   155  func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   156  	log.G(ctx).Debug("Starting Prepare")
   157  	return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
   158  }
   159  
   160  func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
   161  	log.G(ctx).Debug("Starting View")
   162  	return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
   163  }
   164  
   165  // Mounts returns the mounts for the transaction identified by key. Can be
   166  // called on an read-write or readonly transaction.
   167  //
   168  // This can be used to recover mounts after calling View or Prepare.
   169  func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
   170  	log.G(ctx).Debug("Starting Mounts")
   171  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	defer t.Rollback()
   176  
   177  	snapshot, err := storage.GetSnapshot(ctx, key)
   178  	if err != nil {
   179  		return nil, errors.Wrap(err, "failed to get snapshot mount")
   180  	}
   181  	return s.mounts(snapshot), nil
   182  }
   183  
   184  func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
   185  	log.G(ctx).Debug("Starting Commit")
   186  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   187  	if err != nil {
   188  		return err
   189  	}
   190  	defer t.Rollback()
   191  
   192  	usage := fs.Usage{
   193  		Size: 0,
   194  	}
   195  
   196  	if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
   197  		return errors.Wrap(err, "failed to commit snapshot")
   198  	}
   199  
   200  	if err := t.Commit(); err != nil {
   201  		return err
   202  	}
   203  	return nil
   204  }
   205  
   206  // Remove abandons the transaction identified by key. All resources
   207  // associated with the key will be removed.
   208  func (s *snapshotter) Remove(ctx context.Context, key string) error {
   209  	log.G(ctx).Debug("Starting Remove")
   210  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	defer t.Rollback()
   215  
   216  	id, _, err := storage.Remove(ctx, key)
   217  	if err != nil {
   218  		return errors.Wrap(err, "failed to remove")
   219  	}
   220  
   221  	path := s.getSnapshotDir(id)
   222  	renamed := s.getSnapshotDir("rm-" + id)
   223  	if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) {
   224  		return err
   225  	}
   226  
   227  	if err := t.Commit(); err != nil {
   228  		if err1 := os.Rename(renamed, path); err1 != nil {
   229  			// May cause inconsistent data on disk
   230  			log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit")
   231  		}
   232  		return errors.Wrap(err, "failed to commit")
   233  	}
   234  
   235  	if err := os.RemoveAll(renamed); err != nil {
   236  		// Must be cleaned up, any "rm-*" could be removed if no active transactions
   237  		log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem")
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  // Walk the committed snapshots.
   244  func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error {
   245  	log.G(ctx).Debug("Starting Walk")
   246  	ctx, t, err := s.ms.TransactionContext(ctx, false)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	defer t.Rollback()
   251  
   252  	return storage.WalkInfo(ctx, fn)
   253  }
   254  
   255  // Close closes the snapshotter
   256  func (s *snapshotter) Close() error {
   257  	return s.ms.Close()
   258  }
   259  
   260  func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
   261  	var (
   262  		roFlag           string
   263  		source           string
   264  		parentLayerPaths []string
   265  	)
   266  
   267  	if sn.Kind == snapshots.KindView {
   268  		roFlag = "ro"
   269  	} else {
   270  		roFlag = "rw"
   271  	}
   272  
   273  	if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive {
   274  		source = s.getSnapshotDir(sn.ID)
   275  		parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs)
   276  	} else {
   277  		source = s.getSnapshotDir(sn.ParentIDs[0])
   278  		parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:])
   279  	}
   280  
   281  	// error is not checked here, as a string array will never fail to Marshal
   282  	parentLayersJSON, _ := json.Marshal(parentLayerPaths)
   283  	parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON)
   284  
   285  	var mounts []mount.Mount
   286  	mounts = append(mounts, mount.Mount{
   287  		Source: source,
   288  		Type:   "lcow-layer",
   289  		Options: []string{
   290  			roFlag,
   291  			parentLayersOption,
   292  		},
   293  	})
   294  
   295  	return mounts
   296  }
   297  
   298  func (s *snapshotter) getSnapshotDir(id string) string {
   299  	return filepath.Join(s.root, "snapshots", id)
   300  }
   301  
   302  func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
   303  	ctx, t, err := s.ms.TransactionContext(ctx, true)
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	defer t.Rollback()
   308  
   309  	newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
   310  	if err != nil {
   311  		return nil, errors.Wrap(err, "failed to create snapshot")
   312  	}
   313  
   314  	if kind == snapshots.KindActive {
   315  		log.G(ctx).Debug("createSnapshot active")
   316  		// Create the new snapshot dir
   317  		snDir := s.getSnapshotDir(newSnapshot.ID)
   318  		if err := os.MkdirAll(snDir, 0700); err != nil {
   319  			return nil, err
   320  		}
   321  
   322  		scratchSource, err := s.openOrCreateScratch(ctx)
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  		defer scratchSource.Close()
   327  
   328  		// TODO: JTERRY75 - This has to be called sandbox.vhdx for the time
   329  		// being but it really is the scratch.vhdx Using this naming convention
   330  		// for now but this is not the kubernetes sandbox.
   331  		//
   332  		// Create the sandbox.vhdx for this snapshot from the cache.
   333  		destPath := filepath.Join(snDir, "sandbox.vhdx")
   334  		dest, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE, 0700)
   335  		if err != nil {
   336  			return nil, errors.Wrap(err, "failed to create sandbox.vhdx in snapshot")
   337  		}
   338  		defer dest.Close()
   339  		if _, err := io.Copy(dest, scratchSource); err != nil {
   340  			dest.Close()
   341  			os.Remove(destPath)
   342  			return nil, errors.Wrap(err, "failed to copy cached scratch.vhdx to sandbox.vhdx in snapshot")
   343  		}
   344  	}
   345  
   346  	if err := t.Commit(); err != nil {
   347  		return nil, errors.Wrap(err, "commit failed")
   348  	}
   349  
   350  	return s.mounts(newSnapshot), nil
   351  }
   352  
   353  func (s *snapshotter) openOrCreateScratch(ctx context.Context) (_ *os.File, err error) {
   354  	// Create the scratch.vhdx cache file if it doesn't already exit.
   355  	s.scratchLock.Lock()
   356  	defer s.scratchLock.Unlock()
   357  
   358  	scratchFinalPath := filepath.Join(s.root, "scratch.vhdx")
   359  	scratchSource, err := os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700)
   360  	if err != nil {
   361  		if !os.IsNotExist(err) {
   362  			return nil, errors.Wrap(err, "failed to open scratch.vhdx for read")
   363  		}
   364  
   365  		log.G(ctx).Debug("scratch.vhdx not found, creating a new one")
   366  
   367  		// Golang logic for ioutil.TempFile without the file creation
   368  		r := uint32(time.Now().UnixNano() + int64(os.Getpid()))
   369  		r = r*1664525 + 1013904223 // constants from Numerical Recipes
   370  
   371  		scratchTempName := fmt.Sprintf("scratch-%s-tmp.vhdx", strconv.Itoa(int(1e9 + r%1e9))[1:])
   372  		scratchTempPath := filepath.Join(s.root, scratchTempName)
   373  
   374  		// Create the scratch
   375  		rhcs := runhcs.Runhcs{
   376  			Debug:     true,
   377  			Log:       filepath.Join(s.root, "runhcs-scratch.log"),
   378  			LogFormat: runhcs.JSON,
   379  			Owner:     "containerd",
   380  		}
   381  		if err := rhcs.CreateScratch(ctx, scratchTempPath); err != nil {
   382  			_ = os.Remove(scratchTempPath)
   383  			return nil, errors.Wrapf(err, "failed to create '%s' temp file", scratchTempName)
   384  		}
   385  		if err := os.Rename(scratchTempPath, scratchFinalPath); err != nil {
   386  			_ = os.Remove(scratchTempPath)
   387  			return nil, errors.Wrapf(err, "failed to rename '%s' temp file to 'scratch.vhdx'", scratchTempName)
   388  		}
   389  		scratchSource, err = os.OpenFile(scratchFinalPath, os.O_RDONLY, 0700)
   390  		if err != nil {
   391  			_ = os.Remove(scratchFinalPath)
   392  			return nil, errors.Wrap(err, "failed to open scratch.vhdx for read after creation")
   393  		}
   394  	}
   395  	return scratchSource, nil
   396  }
   397  
   398  func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
   399  	var parentLayerPaths []string
   400  	for _, ID := range parentIDs {
   401  		parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID))
   402  	}
   403  	return parentLayerPaths
   404  }