Implement WCOW parentless active snapshots and view snapshots
The WCOW layer support does not support creating sandboxes with no parent. Instead, parentless scratch layers must be layed out as a directory containing only a directory named 'Files', and all data stored inside 'Files'. At commit-time, this will be converted in-place into a read-only layer suitable for use as a parent layer. The WCOW layer support also does not deal with making read-only layers, i.e. layers that are prepared to be parent layers, visible in a read-only manner. A bind-mount or junction point cannot be made read-only, so a view must instead be a small sandbox layer that we can mount via WCOW, and discard later, to protect the layer against accidental or deliberate modification. Signed-off-by: Paul "TBBle" Hampson <Paul.Hampson@Pobox.com>
This commit is contained in:
parent
988ee8ffef
commit
34b07d3e2d
@ -320,10 +320,13 @@ func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
|
||||
return "", nil, fmt.Errorf("number of mounts should always be 1 for Windows layers: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
mnt := mounts[0]
|
||||
if mnt.Type != "windows-layer" {
|
||||
|
||||
if mnt.Type != "windows-layer" && mnt.Type != "bind" {
|
||||
// This is a special case error. When this is received the diff service
|
||||
// will attempt the next differ in the chain which for Windows is the
|
||||
// lcow differ that we want.
|
||||
// TODO: Is there any situation where we actually wanted a "bind" mount to
|
||||
// fall through to the lcow differ?
|
||||
return "", nil, fmt.Errorf("windowsDiff does not support layer type %s: %w", mnt.Type, errdefs.ErrNotImplemented)
|
||||
}
|
||||
|
||||
@ -332,6 +335,30 @@ func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
isView := false
|
||||
for _, o := range mnt.Options {
|
||||
if o == "ro" {
|
||||
isView = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isView {
|
||||
if mnt.Type == "bind" && len(parentLayerPaths) != 0 {
|
||||
return "", nil, fmt.Errorf("unexpected bind-mount View with parents: %w", errdefs.ErrInvalidArgument)
|
||||
} else if mnt.Type == "bind" {
|
||||
// rootfs.CreateDiff creates a new, empty View to diff against,
|
||||
// when diffing something with no parent.
|
||||
// This makes perfect sense for a walking Diff, but for WCOW,
|
||||
// we have to recognise this as "diff against nothing"
|
||||
return "", nil, nil
|
||||
} else if len(parentLayerPaths) == 0 {
|
||||
return "", nil, fmt.Errorf("unexpected windows-layer View with no parent: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
// Ignore the dummy sandbox.
|
||||
return parentLayerPaths[0], parentLayerPaths[1:], nil
|
||||
}
|
||||
|
||||
return mnt.Source, parentLayerPaths, nil
|
||||
}
|
||||
|
||||
@ -346,8 +373,16 @@ func mountPairToLayerStack(lower, upper []mount.Mount) ([]string, error) {
|
||||
return nil, fmt.Errorf("Upper mount invalid: %w", err)
|
||||
}
|
||||
|
||||
lowerLayer, lowerParentLayerPaths, err := mountsToLayerAndParents(lower)
|
||||
if errdefs.IsNotImplemented(err) {
|
||||
// Upper was a windows-layer or bind, lower is not. We can't handle that.
|
||||
return nil, fmt.Errorf("windowsDiff cannot diff a windows-layer against a non-windows-layer: %w", errdefs.ErrInvalidArgument)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("Lower mount invalid: %w", err)
|
||||
}
|
||||
|
||||
// Trivial case, diff-against-nothing
|
||||
if len(lower) == 0 {
|
||||
if lowerLayer == "" {
|
||||
if len(upperParentLayerPaths) != 0 {
|
||||
return nil, fmt.Errorf("windowsDiff cannot diff a layer with parents against a null layer: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
@ -358,14 +393,6 @@ func mountPairToLayerStack(lower, upper []mount.Mount) ([]string, error) {
|
||||
return nil, fmt.Errorf("windowsDiff cannot diff a layer with no parents against another layer: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
|
||||
lowerLayer, lowerParentLayerPaths, err := mountsToLayerAndParents(lower)
|
||||
if errdefs.IsNotImplemented(err) {
|
||||
// Upper was a windows-layer, lower is not. We can't handle that.
|
||||
return nil, fmt.Errorf("windowsDiff cannot diff a windows-layer against a non-windows-layer: %w", errdefs.ErrInvalidArgument)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("Lower mount invalid: %w", err)
|
||||
}
|
||||
|
||||
if upperParentLayerPaths[0] != lowerLayer {
|
||||
return nil, fmt.Errorf("windowsDiff cannot diff a layer against a layer other than its own parent: %w", errdefs.ErrInvalidArgument)
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ func (s *snapshotter) Mounts(ctx context.Context, key string) (_ []mount.Mount,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.mounts(snapshot), nil
|
||||
return s.mounts(snapshot, key), nil
|
||||
}
|
||||
|
||||
func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (retErr error) {
|
||||
@ -208,7 +208,11 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
||||
// If (windowsDiff).Apply was used to populate this layer, then it's already in the 'committed' state.
|
||||
// See createSnapshot below for more details
|
||||
if !strings.Contains(key, snapshots.UnpackKeyPrefix) {
|
||||
if err := s.convertScratchToReadOnlyLayer(ctx, snapshot, path); err != nil {
|
||||
if len(snapshot.ParentIDs) == 0 {
|
||||
if err = hcsshim.ConvertToBaseLayer(path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := s.convertScratchToReadOnlyLayer(ctx, snapshot, path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -299,11 +303,9 @@ func (s *snapshotter) Close() error {
|
||||
return s.ms.Close()
|
||||
}
|
||||
|
||||
func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
|
||||
func (s *snapshotter) mounts(sn storage.Snapshot, key string) []mount.Mount {
|
||||
var (
|
||||
roFlag string
|
||||
source string
|
||||
parentLayerPaths []string
|
||||
roFlag string
|
||||
)
|
||||
|
||||
if sn.Kind == snapshots.KindView {
|
||||
@ -312,12 +314,19 @@ func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
|
||||
roFlag = "rw"
|
||||
}
|
||||
|
||||
if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive {
|
||||
source = s.getSnapshotDir(sn.ID)
|
||||
parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs)
|
||||
} else {
|
||||
source = s.getSnapshotDir(sn.ParentIDs[0])
|
||||
parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:])
|
||||
source := s.getSnapshotDir(sn.ID)
|
||||
parentLayerPaths := s.parentIDsToParentPaths(sn.ParentIDs)
|
||||
|
||||
mountType := "windows-layer"
|
||||
|
||||
if len(sn.ParentIDs) == 0 {
|
||||
// A mount of a parentless snapshot is a bind-mount.
|
||||
mountType = "bind"
|
||||
// If not being extracted into, then the bind-target is the
|
||||
// "Files" subdirectory.
|
||||
if !strings.Contains(key, snapshots.UnpackKeyPrefix) {
|
||||
source = filepath.Join(source, "Files")
|
||||
}
|
||||
}
|
||||
|
||||
// error is not checked here, as a string array will never fail to Marshal
|
||||
@ -327,7 +336,7 @@ func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
|
||||
var mounts []mount.Mount
|
||||
mounts = append(mounts, mount.Mount{
|
||||
Source: source,
|
||||
Type: "windows-layer",
|
||||
Type: mountType,
|
||||
Options: []string{
|
||||
roFlag,
|
||||
parentLayersOption,
|
||||
@ -360,13 +369,22 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
||||
return err
|
||||
}
|
||||
|
||||
// IO/disk space optimization
|
||||
//
|
||||
// We only need one sandbox.vhdx for the container. Skip making one for this
|
||||
// snapshot if this isn't the snapshot that just houses the final sandbox.vhd
|
||||
// that will be mounted as the containers scratch. Currently the key for a snapshot
|
||||
// where a layer will be extracted to will have the string `extract-` in it.
|
||||
if !strings.Contains(key, snapshots.UnpackKeyPrefix) {
|
||||
if strings.Contains(key, snapshots.UnpackKeyPrefix) {
|
||||
// IO/disk space optimization: Do nothing
|
||||
//
|
||||
// We only need one sandbox.vhdx for the container. Skip making one for this
|
||||
// snapshot if this isn't the snapshot that just houses the final sandbox.vhd
|
||||
// that will be mounted as the containers scratch. Currently the key for a snapshot
|
||||
// where a layer will be extracted to will have the string `extract-` in it.
|
||||
} else if len(newSnapshot.ParentIDs) == 0 {
|
||||
// A parentless snapshot is just a bind-mount to a directory named
|
||||
// "Files". When committed, there'll be some post-processing to fill in the rest
|
||||
// of the metadata.
|
||||
filesDir := filepath.Join(snDir, "Files")
|
||||
if err := os.MkdirAll(filesDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs)
|
||||
|
||||
var snapshotInfo snapshots.Info
|
||||
@ -375,22 +393,28 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
||||
}
|
||||
|
||||
var sizeInBytes uint64
|
||||
if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeInGBLabel]; ok {
|
||||
log.G(ctx).Warnf("%q label is deprecated, please use %q instead.", rootfsSizeInGBLabel, rootfsSizeInBytesLabel)
|
||||
if kind == snapshots.KindActive {
|
||||
if sizeGBstr, ok := snapshotInfo.Labels[rootfsSizeInGBLabel]; ok {
|
||||
log.G(ctx).Warnf("%q label is deprecated, please use %q instead.", rootfsSizeInGBLabel, rootfsSizeInBytesLabel)
|
||||
|
||||
sizeInGB, err := strconv.ParseUint(sizeGBstr, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInGBLabel, sizeGBstr, err)
|
||||
sizeInGB, err := strconv.ParseUint(sizeGBstr, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInGBLabel, sizeGBstr, err)
|
||||
}
|
||||
sizeInBytes = sizeInGB * 1024 * 1024 * 1024
|
||||
}
|
||||
sizeInBytes = sizeInGB * 1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
// Prefer the newer label in bytes over the deprecated Windows specific GB variant.
|
||||
if sizeBytesStr, ok := snapshotInfo.Labels[rootfsSizeInBytesLabel]; ok {
|
||||
sizeInBytes, err = strconv.ParseUint(sizeBytesStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInBytesLabel, sizeBytesStr, err)
|
||||
// Prefer the newer label in bytes over the deprecated Windows specific GB variant.
|
||||
if sizeBytesStr, ok := snapshotInfo.Labels[rootfsSizeInBytesLabel]; ok {
|
||||
sizeInBytes, err = strconv.ParseUint(sizeBytesStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse label %q=%q: %w", rootfsSizeInBytesLabel, sizeBytesStr, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A view is just a read-write snapshot with a _really_ small sandbox, since we cannot actually
|
||||
// make a read-only mount or junction point. https://superuser.com/q/881544/112473
|
||||
sizeInBytes = 1024 * 1024 * 1024
|
||||
}
|
||||
|
||||
var makeUVMScratch bool
|
||||
@ -415,7 +439,7 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.mounts(newSnapshot), nil
|
||||
return s.mounts(newSnapshot, key), nil
|
||||
}
|
||||
|
||||
func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
|
||||
|
Loading…
Reference in New Issue
Block a user