Implement Windows snapshotter and differ
This implements the Windows snapshotter and diff Apply function. This allows for Windows layers to be created, and layers to be pulled from the hub. Signed-off-by: Darren Stahl <darst@microsoft.com>
This commit is contained in:
parent
12eaf13f6f
commit
a5a9f91832
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/containerd/containerd/diff/windows"
|
||||||
_ "github.com/containerd/containerd/snapshots/windows"
|
_ "github.com/containerd/containerd/snapshots/windows"
|
||||||
_ "github.com/containerd/containerd/windows"
|
_ "github.com/containerd/containerd/windows"
|
||||||
)
|
)
|
||||||
|
169
diff/windows/windows.go
Normal file
169
diff/windows/windows.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
winio "github.com/Microsoft/go-winio"
|
||||||
|
"github.com/containerd/containerd/archive"
|
||||||
|
"github.com/containerd/containerd/archive/compression"
|
||||||
|
"github.com/containerd/containerd/content"
|
||||||
|
"github.com/containerd/containerd/diff"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/images"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/containerd/containerd/metadata"
|
||||||
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
|
"github.com/containerd/containerd/plugin"
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
plugin.Register(&plugin.Registration{
|
||||||
|
Type: plugin.DiffPlugin,
|
||||||
|
ID: "windows",
|
||||||
|
Requires: []plugin.Type{
|
||||||
|
plugin.MetadataPlugin,
|
||||||
|
},
|
||||||
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||||
|
md, err := ic.Get(plugin.MetadataPlugin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec())
|
||||||
|
return NewWindowsDiff(md.(*metadata.DB).ContentStore())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type windowsDiff struct {
|
||||||
|
store content.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyDesc = ocispec.Descriptor{}
|
||||||
|
|
||||||
|
// NewWindowsDiff is the Windows container layer implementation of diff.Differ.
|
||||||
|
func NewWindowsDiff(store content.Store) (diff.Differ, error) {
|
||||||
|
return &windowsDiff{
|
||||||
|
store: store,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies the content associated with the provided digests onto the
|
||||||
|
// provided mounts. Archive content will be extracted and decompressed if
|
||||||
|
// necessary.
|
||||||
|
func (s *windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (d ocispec.Descriptor, err error) {
|
||||||
|
t1 := time.Now()
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
log.G(ctx).WithFields(logrus.Fields{
|
||||||
|
"d": time.Now().Sub(t1),
|
||||||
|
"dgst": desc.Digest,
|
||||||
|
"size": desc.Size,
|
||||||
|
"media": desc.MediaType,
|
||||||
|
}).Debugf("diff applied")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var isCompressed bool
|
||||||
|
switch desc.MediaType {
|
||||||
|
case ocispec.MediaTypeImageLayer, images.MediaTypeDockerSchema2Layer:
|
||||||
|
case ocispec.MediaTypeImageLayerGzip, images.MediaTypeDockerSchema2LayerGzip:
|
||||||
|
isCompressed = true
|
||||||
|
default:
|
||||||
|
// Still apply all generic media types *.tar[.+]gzip and *.tar
|
||||||
|
if strings.HasSuffix(desc.MediaType, ".tar.gzip") || strings.HasSuffix(desc.MediaType, ".tar+gzip") {
|
||||||
|
isCompressed = true
|
||||||
|
} else if !strings.HasSuffix(desc.MediaType, ".tar") {
|
||||||
|
return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", desc.MediaType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ra, err := s.store.ReaderAt(ctx, desc.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return emptyDesc, errors.Wrap(err, "failed to get reader from content store")
|
||||||
|
}
|
||||||
|
defer ra.Close()
|
||||||
|
|
||||||
|
r := content.NewReader(ra)
|
||||||
|
if isCompressed {
|
||||||
|
ds, err := compression.DecompressStream(r)
|
||||||
|
if err != nil {
|
||||||
|
return emptyDesc, err
|
||||||
|
}
|
||||||
|
defer ds.Close()
|
||||||
|
r = ds
|
||||||
|
}
|
||||||
|
|
||||||
|
digester := digest.Canonical.Digester()
|
||||||
|
rc := &readCounter{
|
||||||
|
r: io.TeeReader(r, digester.Hash()),
|
||||||
|
}
|
||||||
|
|
||||||
|
layer, parentLayerPaths, err := mountsToLayerAndParents(mounts)
|
||||||
|
if err != nil {
|
||||||
|
return emptyDesc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO darrenstahlmsft: When this is done isolated, we should disable these.
|
||||||
|
// it currently cannot be disabled, unless we add ref counting. Since this is
|
||||||
|
// temporary, leaving it enabled is OK for now.
|
||||||
|
if err := winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege}); err != nil {
|
||||||
|
return emptyDesc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := archive.Apply(ctx, layer, rc, archive.WithParentLayers(parentLayerPaths), archive.AsWindowsContainerLayer()); err != nil {
|
||||||
|
return emptyDesc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read any trailing data
|
||||||
|
if _, err := io.Copy(ioutil.Discard, rc); err != nil {
|
||||||
|
return emptyDesc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ocispec.Descriptor{
|
||||||
|
MediaType: ocispec.MediaTypeImageLayer,
|
||||||
|
Size: rc.c,
|
||||||
|
Digest: digester.Digest(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffMounts creates a diff between the given mounts and uploads the result
|
||||||
|
// to the content store.
|
||||||
|
func (s *windowsDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) {
|
||||||
|
panic("not implemented on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
type readCounter struct {
|
||||||
|
r io.Reader
|
||||||
|
c int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rc *readCounter) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = rc.r.Read(p)
|
||||||
|
rc.c += int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
|
||||||
|
if len(mounts) != 1 {
|
||||||
|
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "number of mounts should always be 1 for Windows layers")
|
||||||
|
}
|
||||||
|
layer := mounts[0].Source
|
||||||
|
|
||||||
|
parentLayerPaths, err := mounts[0].GetParentPaths()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer, parentLayerPaths, nil
|
||||||
|
}
|
@ -1,6 +1,13 @@
|
|||||||
package mount
|
package mount
|
||||||
|
|
||||||
import "github.com/pkg/errors"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Microsoft/hcsshim"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrNotImplementOnWindows is returned when an action is not implemented for windows
|
// ErrNotImplementOnWindows is returned when an action is not implemented for windows
|
||||||
@ -9,15 +16,73 @@ var (
|
|||||||
|
|
||||||
// Mount to the provided target
|
// Mount to the provided target
|
||||||
func (m *Mount) Mount(target string) error {
|
func (m *Mount) Mount(target string) error {
|
||||||
return ErrNotImplementOnWindows
|
home, layerID := filepath.Split(m.Source)
|
||||||
|
|
||||||
|
parentLayerPaths, err := m.GetParentPaths()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var di = hcsshim.DriverInfo{
|
||||||
|
HomeDir: home,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = hcsshim.ActivateLayer(di, layerID); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to activate layer %s", m.Source)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
hcsshim.DeactivateLayer(di, layerID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err = hcsshim.PrepareLayer(di, layerID, parentLayerPaths); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to prepare layer %s", m.Source)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
hcsshim.UnprepareLayer(di, layerID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParentLayerPathsFlag is the options flag used to represent the JSON encoded
|
||||||
|
// list of parent layers required to use the layer
|
||||||
|
const ParentLayerPathsFlag = "parentLayerPaths="
|
||||||
|
|
||||||
|
// GetParentPaths of the mount
|
||||||
|
func (m *Mount) GetParentPaths() ([]string, error) {
|
||||||
|
var parentLayerPaths []string
|
||||||
|
for _, option := range m.Options {
|
||||||
|
if strings.HasPrefix(option, ParentLayerPathsFlag) {
|
||||||
|
err := json.Unmarshal([]byte(option[len(ParentLayerPathsFlag):]), &parentLayerPaths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal parent layer paths from mount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parentLayerPaths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmount the mount at the provided path
|
// Unmount the mount at the provided path
|
||||||
func Unmount(mount string, flags int) error {
|
func Unmount(mount string, flags int) error {
|
||||||
return ErrNotImplementOnWindows
|
home, layerID := filepath.Split(mount)
|
||||||
|
var di = hcsshim.DriverInfo{
|
||||||
|
HomeDir: home,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hcsshim.UnprepareLayer(di, layerID); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to unprepare layer %s", mount)
|
||||||
|
}
|
||||||
|
if err := hcsshim.DeactivateLayer(di, layerID); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to deactivate layer %s", mount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmountAll mounts at the provided path
|
// UnmountAll unmounts from the provided path
|
||||||
func UnmountAll(mount string, flags int) error {
|
func UnmountAll(mount string, flags int) error {
|
||||||
return ErrNotImplementOnWindows
|
return Unmount(mount, flags)
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,7 @@ func init() {
|
|||||||
Requires: []plugin.Type{
|
Requires: []plugin.Type{
|
||||||
plugin.DiffPlugin,
|
plugin.DiffPlugin,
|
||||||
},
|
},
|
||||||
Config: &config{
|
Config: defaultDifferConfig,
|
||||||
Order: []string{"walking"},
|
|
||||||
},
|
|
||||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||||
differs, err := ic.GetByType(plugin.DiffPlugin)
|
differs, err := ic.GetByType(plugin.DiffPlugin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
7
services/diff/service_unix.go
Normal file
7
services/diff/service_unix.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
var defaultDifferConfig = &config{
|
||||||
|
Order: []string{"walking"},
|
||||||
|
}
|
7
services/diff/service_windows.go
Normal file
7
services/diff/service_windows.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package diff
|
||||||
|
|
||||||
|
var defaultDifferConfig = &config{
|
||||||
|
Order: []string{"windows"},
|
||||||
|
}
|
16
snapshots/windows/utilities.go
Normal file
16
snapshots/windows/utilities.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package windows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/containerd/containerd/snapshots/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func rollbackWithLogging(ctx context.Context, t storage.Transactor) {
|
||||||
|
if err := t.Rollback(); err != nil {
|
||||||
|
log.G(ctx).WithError(err).Warn("failed to rollback transaction")
|
||||||
|
}
|
||||||
|
}
|
@ -4,16 +4,23 @@ package windows
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Microsoft/hcsshim"
|
||||||
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/fs"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/snapshots"
|
"github.com/containerd/containerd/snapshots"
|
||||||
|
"github.com/containerd/containerd/snapshots/storage"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNotImplemented is returned when an action is not implemented
|
|
||||||
ErrNotImplemented = errors.New("not implemented")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -28,12 +35,38 @@ func init() {
|
|||||||
|
|
||||||
type snapshotter struct {
|
type snapshotter struct {
|
||||||
root string
|
root string
|
||||||
|
info hcsshim.DriverInfo
|
||||||
|
ms *storage.MetaStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSnapshotter returns a new windows snapshotter
|
// NewSnapshotter returns a new windows snapshotter
|
||||||
func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
|
func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
|
||||||
|
fsType, err := getFileSystemType(string(root[0]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if strings.ToLower(fsType) == "refs" {
|
||||||
|
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is on an ReFS volume - ReFS volumes are not supported", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(root, 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &snapshotter{
|
return &snapshotter{
|
||||||
|
info: hcsshim.DriverInfo{
|
||||||
|
HomeDir: filepath.Join(root, "snapshots"),
|
||||||
|
},
|
||||||
root: root,
|
root: root,
|
||||||
|
ms: ms,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,50 +75,298 @@ func NewSnapshotter(root string) (snapshots.Snapshotter, error) {
|
|||||||
//
|
//
|
||||||
// Should be used for parent resolution, existence checks and to discern
|
// Should be used for parent resolution, existence checks and to discern
|
||||||
// the kind of snapshot.
|
// the kind of snapshot.
|
||||||
func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
|
func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return snapshots.Info{}, err
|
||||||
|
}
|
||||||
|
defer t.Rollback()
|
||||||
|
|
||||||
|
_, info, _, err := storage.GetInfo(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return snapshots.Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
|
func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return snapshots.Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var committed bool
|
||||||
|
defer func() {
|
||||||
|
if committed == false {
|
||||||
|
rollbackWithLogging(ctx, t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
|
||||||
|
if err != nil {
|
||||||
|
return snapshots.Info{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.Commit(); err != nil {
|
||||||
|
return snapshots.Info{}, err
|
||||||
|
}
|
||||||
|
committed = true
|
||||||
|
|
||||||
|
return info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
|
func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return snapshots.Usage{}, err
|
||||||
|
}
|
||||||
|
defer t.Rollback()
|
||||||
|
|
||||||
|
_, info, usage, err := storage.GetInfo(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return snapshots.Usage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Kind == snapshots.KindActive {
|
||||||
|
du := fs.Usage{
|
||||||
|
Size: 0,
|
||||||
|
}
|
||||||
|
usage = snapshots.Usage(du)
|
||||||
|
}
|
||||||
|
|
||||||
|
return usage, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
|
func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
|
||||||
panic("not implemented")
|
return s.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
|
func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
|
||||||
panic("not implemented")
|
return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mounts returns the mounts for the transaction identified by key. Can be
|
// Mounts returns the mounts for the transaction identified by key. Can be
|
||||||
// called on an read-write or readonly transaction.
|
// called on an read-write or readonly transaction.
|
||||||
//
|
//
|
||||||
// This can be used to recover mounts after calling View or Prepare.
|
// This can be used to recover mounts after calling View or Prepare.
|
||||||
func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
|
func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer t.Rollback()
|
||||||
|
snapshot, err := storage.GetSnapshot(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get snapshot mount")
|
||||||
|
}
|
||||||
|
return s.mounts(snapshot), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
|
func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var committed bool
|
||||||
|
defer func() {
|
||||||
|
if committed == false {
|
||||||
|
rollbackWithLogging(ctx, t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
usage := fs.Usage{
|
||||||
|
Size: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to commit snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.Commit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
committed = true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove abandons the transaction identified by key. All resources
|
// Remove abandons the transaction identified by key. All resources
|
||||||
// associated with the key will be removed.
|
// associated with the key will be removed.
|
||||||
func (o *snapshotter) Remove(ctx context.Context, key string) error {
|
func (s *snapshotter) Remove(ctx context.Context, key string) error {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var committed bool
|
||||||
|
defer func() {
|
||||||
|
if committed == false {
|
||||||
|
rollbackWithLogging(ctx, t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
id, _, err := storage.Remove(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := s.getSnapshotDir(id)
|
||||||
|
renamedID := "rm-" + id
|
||||||
|
renamed := filepath.Join(s.root, "snapshots", "rm-"+id)
|
||||||
|
if err := os.Rename(path, renamed); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.Commit()
|
||||||
|
if err != nil {
|
||||||
|
if err1 := os.Rename(renamed, path); err1 != nil {
|
||||||
|
// May cause inconsistent data on disk
|
||||||
|
log.G(ctx).WithError(err1).WithField("path", renamed).Errorf("Failed to rename after failed commit")
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "failed to commit")
|
||||||
|
}
|
||||||
|
committed = true
|
||||||
|
|
||||||
|
if err := hcsshim.DestroyLayer(s.info, renamedID); err != nil {
|
||||||
|
// Must be cleaned up, any "rm-*" could be removed if no active transactions
|
||||||
|
log.G(ctx).WithError(err).WithField("path", renamed).Warnf("Failed to remove root filesystem")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the committed snapshots.
|
// Walk the committed snapshots.
|
||||||
func (o *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error {
|
func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error {
|
||||||
panic("not implemented")
|
ctx, t, err := s.ms.TransactionContext(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer t.Rollback()
|
||||||
|
return storage.WalkInfo(ctx, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the snapshotter
|
// Close closes the snapshotter
|
||||||
func (o *snapshotter) Close() error {
|
func (s *snapshotter) Close() error {
|
||||||
panic("not implemented")
|
return s.ms.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount {
|
||||||
|
var (
|
||||||
|
roFlag string
|
||||||
|
)
|
||||||
|
|
||||||
|
if sn.Kind == snapshots.KindView {
|
||||||
|
roFlag = "ro"
|
||||||
|
} else {
|
||||||
|
roFlag = "rw"
|
||||||
|
}
|
||||||
|
|
||||||
|
parentLayerPaths := s.parentIDsToParentPaths(sn.ParentIDs)
|
||||||
|
// error is not checked here, as a string array will never fail to Marshal
|
||||||
|
parentLayersJSON, _ := json.Marshal(parentLayerPaths)
|
||||||
|
parentLayersOption := mount.ParentLayerPathsFlag + string(parentLayersJSON)
|
||||||
|
|
||||||
|
var mounts []mount.Mount
|
||||||
|
mounts = append(mounts, mount.Mount{
|
||||||
|
Source: s.getSnapshotDir(sn.ID),
|
||||||
|
Type: "windows-layer",
|
||||||
|
Options: []string{
|
||||||
|
roFlag,
|
||||||
|
parentLayersOption,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return mounts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshotter) getSnapshotDir(id string) string {
|
||||||
|
return filepath.Join(s.root, "snapshots", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) ([]mount.Mount, error) {
|
||||||
|
ctx, t, err := s.ms.TransactionContext(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var committed bool
|
||||||
|
defer func() {
|
||||||
|
if committed == false {
|
||||||
|
rollbackWithLogging(ctx, t)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case snapshots.KindView:
|
||||||
|
var parentID string
|
||||||
|
if len(newSnapshot.ParentIDs) != 0 {
|
||||||
|
parentID = newSnapshot.ParentIDs[0]
|
||||||
|
}
|
||||||
|
if err := hcsshim.CreateLayer(s.info, newSnapshot.ID, parentID); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create layer")
|
||||||
|
}
|
||||||
|
case snapshots.KindActive:
|
||||||
|
parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs)
|
||||||
|
|
||||||
|
var parentPath string
|
||||||
|
if len(parentLayerPaths) != 0 {
|
||||||
|
parentPath = parentLayerPaths[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := hcsshim.CreateSandboxLayer(s.info, newSnapshot.ID, parentPath, parentLayerPaths); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create sandbox layer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(darrenstahlmsft): Allow changing sandbox size
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.Commit(); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "commit failed")
|
||||||
|
}
|
||||||
|
committed = true
|
||||||
|
|
||||||
|
return s.mounts(newSnapshot), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string {
|
||||||
|
var parentLayerPaths []string
|
||||||
|
for _, ID := range parentIDs {
|
||||||
|
parentLayerPaths = append(parentLayerPaths, s.getSnapshotDir(ID))
|
||||||
|
}
|
||||||
|
return parentLayerPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFileSystemType obtains the type of a file system through GetVolumeInformation
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364993(v=vs.85).aspx
|
||||||
|
func getFileSystemType(drive string) (fsType string, hr error) {
|
||||||
|
var (
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
procGetVolumeInformation = modkernel32.NewProc("GetVolumeInformationW")
|
||||||
|
buf = make([]uint16, 255)
|
||||||
|
size = windows.MAX_PATH + 1
|
||||||
|
)
|
||||||
|
if len(drive) != 1 {
|
||||||
|
return "", errors.New("getFileSystemType must be called with a drive letter")
|
||||||
|
}
|
||||||
|
drive += `:\`
|
||||||
|
n := uintptr(unsafe.Pointer(nil))
|
||||||
|
r0, _, _ := syscall.Syscall9(procGetVolumeInformation.Addr(), 8, uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(drive))), n, n, n, n, n, uintptr(unsafe.Pointer(&buf[0])), uintptr(size), 0)
|
||||||
|
if int32(r0) < 0 {
|
||||||
|
hr = syscall.Errno(win32FromHresult(r0))
|
||||||
|
}
|
||||||
|
fsType = windows.UTF16ToString(buf)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32FromHresult is a helper function to get the win32 error code from an HRESULT
|
||||||
|
func win32FromHresult(hr uintptr) uintptr {
|
||||||
|
if hr&0x1fff0000 == 0x00070000 {
|
||||||
|
return hr & 0xffff
|
||||||
|
}
|
||||||
|
return hr
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user