From a5a9f91832f9f414a24626173ad5471df3abdb27 Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Fri, 5 Jan 2018 13:27:20 -0800 Subject: [PATCH 1/6] 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 --- cmd/containerd/builtins_windows.go | 1 + diff/windows/windows.go | 169 +++++++++++++++ mount/mount_windows.go | 75 ++++++- services/diff/service.go | 4 +- services/diff/service_unix.go | 7 + services/diff/service_windows.go | 7 + snapshots/windows/utilities.go | 16 ++ snapshots/windows/windows.go | 331 ++++++++++++++++++++++++++--- 8 files changed, 577 insertions(+), 33 deletions(-) create mode 100644 diff/windows/windows.go create mode 100644 services/diff/service_unix.go create mode 100644 services/diff/service_windows.go create mode 100644 snapshots/windows/utilities.go diff --git a/cmd/containerd/builtins_windows.go b/cmd/containerd/builtins_windows.go index 381489170..95c9fb2f7 100644 --- a/cmd/containerd/builtins_windows.go +++ b/cmd/containerd/builtins_windows.go @@ -1,6 +1,7 @@ package main import ( + _ "github.com/containerd/containerd/diff/windows" _ "github.com/containerd/containerd/snapshots/windows" _ "github.com/containerd/containerd/windows" ) diff --git a/diff/windows/windows.go b/diff/windows/windows.go new file mode 100644 index 000000000..16bffca1e --- /dev/null +++ b/diff/windows/windows.go @@ -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 +} diff --git a/mount/mount_windows.go b/mount/mount_windows.go index 8ad7eab12..df25ea2a8 100644 --- a/mount/mount_windows.go +++ b/mount/mount_windows.go @@ -1,6 +1,13 @@ package mount -import "github.com/pkg/errors" +import ( + "encoding/json" + "path/filepath" + "strings" + + "github.com/Microsoft/hcsshim" + "github.com/pkg/errors" +) var ( // ErrNotImplementOnWindows is returned when an action is not implemented for windows @@ -9,15 +16,73 @@ var ( // Mount to the provided target 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 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 { - return ErrNotImplementOnWindows + return Unmount(mount, flags) } diff --git a/services/diff/service.go b/services/diff/service.go index f847693a4..4e1c9fcc6 100644 --- a/services/diff/service.go +++ b/services/diff/service.go @@ -30,9 +30,7 @@ func init() { Requires: []plugin.Type{ plugin.DiffPlugin, }, - Config: &config{ - Order: []string{"walking"}, - }, + Config: defaultDifferConfig, InitFn: func(ic *plugin.InitContext) (interface{}, error) { differs, err := ic.GetByType(plugin.DiffPlugin) if err != nil { diff --git a/services/diff/service_unix.go b/services/diff/service_unix.go new file mode 100644 index 000000000..39d88bbbb --- /dev/null +++ b/services/diff/service_unix.go @@ -0,0 +1,7 @@ +// +build !windows + +package diff + +var defaultDifferConfig = &config{ + Order: []string{"walking"}, +} diff --git a/services/diff/service_windows.go b/services/diff/service_windows.go new file mode 100644 index 000000000..afe193287 --- /dev/null +++ b/services/diff/service_windows.go @@ -0,0 +1,7 @@ +// +build windows + +package diff + +var defaultDifferConfig = &config{ + Order: []string{"windows"}, +} diff --git a/snapshots/windows/utilities.go b/snapshots/windows/utilities.go new file mode 100644 index 000000000..1854df66c --- /dev/null +++ b/snapshots/windows/utilities.go @@ -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") + } +} diff --git a/snapshots/windows/windows.go b/snapshots/windows/windows.go index 603428e17..be859baa2 100644 --- a/snapshots/windows/windows.go +++ b/snapshots/windows/windows.go @@ -4,16 +4,23 @@ package windows import ( "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/plugin" "github.com/containerd/containerd/snapshots" + "github.com/containerd/containerd/snapshots/storage" "github.com/pkg/errors" -) - -var ( - // ErrNotImplemented is returned when an action is not implemented - ErrNotImplemented = errors.New("not implemented") + "golang.org/x/sys/windows" ) func init() { @@ -28,12 +35,38 @@ func init() { type snapshotter struct { root string + info hcsshim.DriverInfo + ms *storage.MetaStore } // NewSnapshotter returns a new windows snapshotter 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{ + info: hcsshim.DriverInfo{ + HomeDir: filepath.Join(root, "snapshots"), + }, root: root, + ms: ms, }, nil } @@ -42,50 +75,298 @@ func NewSnapshotter(root string) (snapshots.Snapshotter, error) { // // Should be used for parent resolution, existence checks and to discern // the kind of snapshot. -func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { - panic("not implemented") +func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { + 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) { - panic("not implemented") +func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { + 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) { - panic("not implemented") +func (s *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { + 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) { - panic("not implemented") +func (s *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + 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) { - panic("not implemented") +func (s *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { + return s.createSnapshot(ctx, snapshots.KindView, key, parent, opts) } // Mounts returns the mounts for the transaction identified by key. Can be // called on an read-write or readonly transaction. // // This can be used to recover mounts after calling View or Prepare. -func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { - panic("not implemented") +func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { + 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 { - panic("not implemented") +func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { + 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 // associated with the key will be removed. -func (o *snapshotter) Remove(ctx context.Context, key string) error { - panic("not implemented") +func (s *snapshotter) Remove(ctx context.Context, key string) error { + 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. -func (o *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error { - panic("not implemented") +func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapshots.Info) error) error { + 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 -func (o *snapshotter) Close() error { - panic("not implemented") +func (s *snapshotter) Close() error { + 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 } From dcff993653743b37877e2b1d754433ecce75cd4c Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Fri, 5 Jan 2018 13:30:26 -0800 Subject: [PATCH 2/6] Update Windows runtime to use snapshotter and differ layers This changes the Windows runtime to use the snapshotter and differ created layers, and updates the ctr commands to use the snapshotter and differ. Signed-off-by: Darren Stahl --- cmd/ctr/commands/run/run_windows.go | 65 +++++++---------- diff/windows/windows.go | 2 +- mount/mount_windows.go | 15 ++-- snapshots/windows/utilities.go | 16 ----- snapshots/windows/windows.go | 68 +++++------------- windows/hcsshim.go | 80 +++------------------ windows/meta.go | 54 -------------- windows/runtime.go | 107 +++++++++------------------- windows/task.go | 3 +- 9 files changed, 92 insertions(+), 318 deletions(-) delete mode 100644 snapshots/windows/utilities.go delete mode 100644 windows/meta.go diff --git a/cmd/ctr/commands/run/run_windows.go b/cmd/ctr/commands/run/run_windows.go index c33f2302b..82fca433a 100644 --- a/cmd/ctr/commands/run/run_windows.go +++ b/cmd/ctr/commands/run/run_windows.go @@ -7,32 +7,12 @@ import ( "github.com/containerd/containerd" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/containers" - "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) -func init() { - Command.Flags = append(Command.Flags, cli.StringSliceFlag{ - Name: "layer", - Usage: "HCSSHIM Layers to be used", - }) -} - -func withLayers(context *cli.Context) oci.SpecOpts { - return func(ctx gocontext.Context, client oci.Client, c *containers.Container, s *specs.Spec) error { - l := context.StringSlice("layer") - if l == nil { - return errors.Wrap(errdefs.ErrInvalidArgument, "base layers must be specified with `--layer`") - } - s.Windows.LayerFolders = l - return nil - } -} - func withTTY(terminal bool) oci.SpecOpts { if !terminal { return func(ctx gocontext.Context, client oci.Client, c *containers.Container, s *specs.Spec) error { @@ -51,36 +31,39 @@ func withTTY(terminal bool) oci.SpecOpts { func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) { var ( - // ref = context.Args().First() - id = context.Args().Get(1) - args = context.Args()[2:] - tty = context.Bool("tty") - labelStrings = context.StringSlice("label") + ref = context.Args().First() + id = context.Args().Get(1) + args = context.Args()[2:] ) - labels := commands.LabelArgs(labelStrings) - - // TODO(mlaventure): get base image once we have a snapshotter - - opts := []oci.SpecOpts{ - // TODO(mlaventure): use oci.WithImageConfig once we have a snapshotter - withLayers(context), - oci.WithEnv(context.StringSlice("env")), - withMounts(context), - withTTY(tty), + image, err := client.GetImage(ctx, ref) + if err != nil { + return nil, err } + + var ( + opts []oci.SpecOpts + cOpts []containerd.NewContainerOpts + ) + opts = append(opts, oci.WithImageConfig(image)) + opts = append(opts, oci.WithEnv(context.StringSlice("env"))) + opts = append(opts, withMounts(context)) if len(args) > 0 { opts = append(opts, oci.WithProcessArgs(args...)) } if cwd := context.String("cwd"); cwd != "" { opts = append(opts, oci.WithProcessCwd(cwd)) } - return client.NewContainer(ctx, id, - containerd.WithNewSpec(opts...), - containerd.WithContainerLabels(labels), - containerd.WithRuntime(context.String("runtime"), nil), - // TODO(mlaventure): containerd.WithImage(image), - ) + opts = append(opts, withTTY(context.Bool("tty"))) + + cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) + cOpts = append(cOpts, containerd.WithImage(image)) + cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter"))) + cOpts = append(cOpts, containerd.WithNewSnapshot(id, image)) + cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil)) + + cOpts = append([]containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}, cOpts...) + return client.NewContainer(ctx, id, cOpts...) } func getNewTaskOpts(_ *cli.Context) []containerd.NewTaskOpts { diff --git a/diff/windows/windows.go b/diff/windows/windows.go index 16bffca1e..b288027c9 100644 --- a/diff/windows/windows.go +++ b/diff/windows/windows.go @@ -140,7 +140,7 @@ func (s *windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts // 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") + return emptyDesc, errdefs.ErrNotImplemented } type readCounter struct { diff --git a/mount/mount_windows.go b/mount/mount_windows.go index df25ea2a8..6d37fda2a 100644 --- a/mount/mount_windows.go +++ b/mount/mount_windows.go @@ -39,11 +39,6 @@ func (m *Mount) Mount(target string) error { 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 } @@ -67,10 +62,12 @@ func (m *Mount) GetParentPaths() ([]string, error) { // Unmount the mount at the provided path func Unmount(mount string, flags int) error { - home, layerID := filepath.Split(mount) - var di = hcsshim.DriverInfo{ - HomeDir: home, - } + var ( + home, layerID = filepath.Split(mount) + di = hcsshim.DriverInfo{ + HomeDir: home, + } + ) if err := hcsshim.UnprepareLayer(di, layerID); err != nil { return errors.Wrapf(err, "failed to unprepare layer %s", mount) diff --git a/snapshots/windows/utilities.go b/snapshots/windows/utilities.go deleted file mode 100644 index 1854df66c..000000000 --- a/snapshots/windows/utilities.go +++ /dev/null @@ -1,16 +0,0 @@ -// +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") - } -} diff --git a/snapshots/windows/windows.go b/snapshots/windows/windows.go index be859baa2..3d0951300 100644 --- a/snapshots/windows/windows.go +++ b/snapshots/windows/windows.go @@ -41,12 +41,12 @@ type snapshotter struct { // NewSnapshotter returns a new windows snapshotter func NewSnapshotter(root string) (snapshots.Snapshotter, error) { - fsType, err := getFileSystemType(string(root[0])) + fsType, err := getFileSystemType(root) 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 strings.ToLower(fsType) != "ntfs" { + return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "%s is not on an NTFS volume - only NTFS volumes are supported", root) } if err := os.MkdirAll(root, 0700); err != nil { @@ -83,11 +83,7 @@ func (s *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, err defer t.Rollback() _, info, _, err := storage.GetInfo(ctx, key) - if err != nil { - return snapshots.Info{}, err - } - - return info, nil + return info, err } func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { @@ -95,13 +91,7 @@ func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpath if err != nil { return snapshots.Info{}, err } - - var committed bool - defer func() { - if committed == false { - rollbackWithLogging(ctx, t) - } - }() + defer t.Rollback() info, err = storage.UpdateInfo(ctx, info, fieldpaths...) if err != nil { @@ -111,7 +101,6 @@ func (s *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpath if err := t.Commit(); err != nil { return snapshots.Info{}, err } - committed = true return info, nil } @@ -156,6 +145,7 @@ func (s *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, er 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") @@ -168,13 +158,8 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap if err != nil { return err } + defer t.Rollback() - var committed bool - defer func() { - if committed == false { - rollbackWithLogging(ctx, t) - } - }() usage := fs.Usage{ Size: 0, } @@ -186,7 +171,6 @@ func (s *snapshotter) Commit(ctx context.Context, name, key string, opts ...snap if err := t.Commit(); err != nil { return err } - committed = true return nil } @@ -197,13 +181,7 @@ func (s *snapshotter) Remove(ctx context.Context, key string) error { if err != nil { return err } - - var committed bool - defer func() { - if committed == false { - rollbackWithLogging(ctx, t) - } - }() + defer t.Rollback() id, _, err := storage.Remove(ctx, key) if err != nil { @@ -217,15 +195,13 @@ func (s *snapshotter) Remove(ctx context.Context, key string) error { return err } - err = t.Commit() - if err != nil { + if err := t.Commit(); 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 @@ -242,6 +218,7 @@ func (s *snapshotter) Walk(ctx context.Context, fn func(context.Context, snapsho return err } defer t.Rollback() + return storage.WalkInfo(ctx, fn) } @@ -251,9 +228,7 @@ func (s *snapshotter) Close() error { } func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount { - var ( - roFlag string - ) + var roFlag string if sn.Kind == snapshots.KindView { roFlag = "ro" @@ -288,13 +263,7 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k if err != nil { return nil, err } - - var committed bool - defer func() { - if committed == false { - rollbackWithLogging(ctx, t) - } - }() + defer t.Rollback() newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) if err != nil { @@ -328,7 +297,6 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k if err := t.Commit(); err != nil { return nil, errors.Wrap(err, "commit failed") } - committed = true return s.mounts(newSnapshot), nil } @@ -343,17 +311,19 @@ func (s *snapshotter) parentIDsToParentPaths(parentIDs []string) []string { // 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) { +func getFileSystemType(path string) (fsType string, hr error) { + drive := filepath.VolumeName(path) + if len(drive) != 2 { + return "", errors.New("getFileSystemType path must start with a drive letter") + } + 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 += `:\` + 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 { diff --git a/windows/hcsshim.go b/windows/hcsshim.go index 7801da749..8d9501361 100644 --- a/windows/hcsshim.go +++ b/windows/hcsshim.go @@ -4,14 +4,12 @@ package windows import ( "context" - "fmt" "os" "path/filepath" "strings" "github.com/Microsoft/hcsshim" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/log" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -49,14 +47,14 @@ func newWindowsContainerConfig(ctx context.Context, owner, id string, spec *spec } conf.IgnoreFlushesDuringBoot = spec.Windows.IgnoreFlushesDuringBoot - if len(spec.Windows.LayerFolders) < 1 { + if len(spec.Windows.LayerFolders) < 2 { return nil, errors.Wrap(errdefs.ErrInvalidArgument, - "spec.Windows.LayerFolders must have at least 1 layers") + "spec.Windows.LayerFolders must have at least 2 layers") } var ( - layerFolders = spec.Windows.LayerFolders - homeDir = filepath.Dir(layerFolders[0]) - layerFolderPath = filepath.Join(homeDir, id) + layerFolderPath = spec.Windows.LayerFolders[0] + layerFolders = spec.Windows.LayerFolders[1:] + layerID = filepath.Base(layerFolderPath) ) // TODO: use the create request Mount for those @@ -71,39 +69,12 @@ func newWindowsContainerConfig(ctx context.Context, owner, id string, spec *spec Path: layerPath, }) } - - var ( - di = hcsshim.DriverInfo{ - Flavour: 1, // filter driver - HomeDir: homeDir, - } - ) conf.LayerFolderPath = layerFolderPath - // TODO: Once there is a snapshotter for windows, this can be deleted. - // The R/W Layer should come from the Rootfs Mounts provided - // - // Windows doesn't support creating a container with a readonly - // filesystem, so always create a RW one - if err = hcsshim.CreateSandboxLayer(di, id, layerFolders[0], layerFolders); err != nil { - return nil, errors.Wrapf(err, "failed to create sandbox layer for %s: layers: %#v, driverInfo: %#v", - id, layerFolders, di) + var di = hcsshim.DriverInfo{ + HomeDir: filepath.Dir(layerFolderPath), } - defer func() { - if err != nil { - removeLayer(ctx, conf.LayerFolderPath) - } - }() - - if err = hcsshim.ActivateLayer(di, id); err != nil { - return nil, errors.Wrapf(err, "failed to activate layer %s", conf.LayerFolderPath) - } - - if err = hcsshim.PrepareLayer(di, id, layerFolders); err != nil { - return nil, errors.Wrapf(err, "failed to prepare layer %s", conf.LayerFolderPath) - } - - conf.VolumePath, err = hcsshim.GetLayerMountPath(di, id) + conf.VolumePath, err = hcsshim.GetLayerMountPath(di, layerID) if err != nil { return nil, errors.Wrapf(err, "failed to getmount path for layer %s: driverInfo: %#v", id, di) } @@ -146,41 +117,6 @@ func newWindowsContainerConfig(ctx context.Context, owner, id string, spec *spec return conf, nil } -// removeLayer deletes the given layer, all associated containers must have -// been shutdown for this to succeed. -func removeLayer(ctx context.Context, path string) error { - var ( - err error - layerID = filepath.Base(path) - parentPath = filepath.Dir(path) - di = hcsshim.DriverInfo{ - Flavour: 1, // filter driver - HomeDir: parentPath, - } - ) - - if err = hcsshim.UnprepareLayer(di, layerID); err != nil { - log.G(ctx).WithError(err).Warnf("failed to unprepare layer %s for removal", path) - } - - if err = hcsshim.DeactivateLayer(di, layerID); err != nil { - log.G(ctx).WithError(err).Warnf("failed to deactivate layer %s for removal", path) - } - - removePath := filepath.Join(parentPath, fmt.Sprintf("%s-removing", layerID)) - if err = os.Rename(path, removePath); err != nil { - log.G(ctx).WithError(err).Warnf("failed to rename container layer %s for removal", path) - removePath = path - } - - if err = hcsshim.DestroyLayer(di, removePath); err != nil { - log.G(ctx).WithError(err).Errorf("failed to remove container layer %s", removePath) - return err - } - - return nil -} - func newProcessConfig(processSpec *specs.Process, pset *pipeSet) *hcsshim.ProcessConfig { conf := &hcsshim.ProcessConfig{ EmulateConsole: pset.src.Terminal, diff --git a/windows/meta.go b/windows/meta.go deleted file mode 100644 index 81ab9245e..000000000 --- a/windows/meta.go +++ /dev/null @@ -1,54 +0,0 @@ -// +build windows - -package windows - -// TODO: remove this file (i.e. meta.go) once we have a snapshotter - -import ( - "github.com/boltdb/bolt" - "github.com/containerd/containerd/errdefs" - "github.com/pkg/errors" -) - -func newLayerFolderStore(tx *bolt.Tx) *layerFolderStore { - return &layerFolderStore{tx} -} - -type layerFolderStore struct { - tx *bolt.Tx -} - -func (s *layerFolderStore) Create(id, layer string) error { - bkt, err := s.tx.CreateBucketIfNotExists([]byte(pluginID)) - if err != nil { - return errors.Wrapf(err, "failed to create bucket %s", pluginID) - } - err = bkt.Put([]byte(id), []byte(layer)) - if err != nil { - return errors.Wrapf(err, "failed to store entry %s:%s", id, layer) - } - - return nil -} - -func (s *layerFolderStore) Get(id string) (string, error) { - bkt := s.tx.Bucket([]byte(pluginID)) - if bkt == nil { - return "", errors.Wrapf(errdefs.ErrNotFound, "bucket %s", pluginID) - } - - return string(bkt.Get([]byte(id))), nil -} - -func (s *layerFolderStore) Delete(id string) error { - bkt := s.tx.Bucket([]byte(pluginID)) - if bkt == nil { - return errors.Wrapf(errdefs.ErrNotFound, "bucket %s", pluginID) - } - - if err := bkt.Delete([]byte(id)); err != nil { - return errors.Wrapf(err, "failed to delete entry %s", id) - } - - return nil -} diff --git a/windows/runtime.go b/windows/runtime.go index fd26b270c..11423b31b 100644 --- a/windows/runtime.go +++ b/windows/runtime.go @@ -10,13 +10,11 @@ import ( "time" "github.com/Microsoft/hcsshim" - "github.com/boltdb/bolt" eventstypes "github.com/containerd/containerd/api/events" - containerdtypes "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/events" "github.com/containerd/containerd/log" - "github.com/containerd/containerd/metadata" + "github.com/containerd/containerd/mount" "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/plugin" "github.com/containerd/containerd/runtime" @@ -55,12 +53,6 @@ func New(ic *plugin.InitContext) (interface{}, error) { if err := os.MkdirAll(ic.Root, 0700); err != nil { return nil, errors.Wrapf(err, "could not create state directory at %s", ic.Root) } - - m, err := ic.Get(plugin.MetadataPlugin) - if err != nil { - return nil, err - } - r := &windowsRuntime{ root: ic.Root, pidPool: newPidPool(), @@ -70,7 +62,6 @@ func New(ic *plugin.InitContext) (interface{}, error) { // TODO(mlaventure): windows needs a stat monitor monitor: nil, tasks: runtime.NewTaskList(), - db: m.(*metadata.DB), } // Load our existing containers and kill/delete them. We don't support @@ -91,7 +82,6 @@ type windowsRuntime struct { monitor runtime.TaskMonitor tasks *runtime.TaskList - db *metadata.DB } func (r *windowsRuntime) ID() string { @@ -124,8 +114,14 @@ func (r *windowsRuntime) Create(ctx context.Context, id string, opts runtime.Cre if createOpts.TerminateDuration == 0 { createOpts.TerminateDuration = defaultTerminateDuration } + spec.Windows.LayerFolders = append(spec.Windows.LayerFolders, opts.Rootfs[0].Source) + parentLayerPaths, err := opts.Rootfs[0].GetParentPaths() + if err != nil { + return nil, err + } + spec.Windows.LayerFolders = append(spec.Windows.LayerFolders, parentLayerPaths...) - return r.newTask(ctx, namespace, id, spec, opts.IO, createOpts) + return r.newTask(ctx, namespace, id, opts.Rootfs, spec, opts.IO, createOpts) } func (r *windowsRuntime) Get(ctx context.Context, id string) (runtime.Task, error) { @@ -209,14 +205,19 @@ func (r *windowsRuntime) Delete(ctx context.Context, t runtime.Task) (*runtime.E ns, _ := namespaces.Namespace(ctx) serviceCtx := log.WithLogger(context.Background(), log.GetLogger(ctx)) serviceCtx = namespaces.WithNamespace(serviceCtx, ns) - r.serviceTask(serviceCtx, ns, wt.id+"_servicing", wt.spec) + r.serviceTask(serviceCtx, ns, wt.id+"_servicing", wt.rootfs, wt.spec) + } + + if err := mount.UnmountAll(wt.rootfs[0].Source, 0); err != nil { + log.G(ctx).WithError(err).WithField("path", wt.rootfs[0].Source). + Warn("failed to unmount rootfs on failure") } // We were never started, return failure return rtExit, nil } -func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec *specs.Spec, io runtime.IO, createOpts *hcsshimtypes.CreateOptions) (*task, error) { +func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, rootfs []mount.Mount, spec *specs.Spec, io runtime.IO, createOpts *hcsshimtypes.CreateOptions) (*task, error) { var ( err error pset *pipeSet @@ -241,6 +242,18 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec } }() + if err := mount.All(rootfs, ""); err != nil { + return nil, errors.Wrap(err, "failed to mount rootfs") + } + defer func() { + if err != nil { + if err := mount.UnmountAll(rootfs[0].Source, 0); err != nil { + log.G(ctx).WithError(err).WithField("path", rootfs[0].Source). + Warn("failed to unmount rootfs on failure") + } + } + }() + var ( conf *hcsshim.ContainerConfig nsid = namespace + "-" + id @@ -248,31 +261,6 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec if conf, err = newWindowsContainerConfig(ctx, hcsshimOwner, nsid, spec); err != nil { return nil, err } - defer func() { - if err != nil { - removeLayer(ctx, conf.LayerFolderPath) - } - }() - - // TODO: remove this once we have a windows snapshotter - // Store the LayerFolder in the db so we can clean it if we die - if err = r.db.Update(func(tx *bolt.Tx) error { - s := newLayerFolderStore(tx) - return s.Create(nsid, conf.LayerFolderPath) - }); err != nil { - return nil, err - } - defer func() { - if err != nil { - if dbErr := r.db.Update(func(tx *bolt.Tx) error { - s := newLayerFolderStore(tx) - return s.Delete(nsid) - }); dbErr != nil { - log.G(ctx).WithField("id", id). - Error("failed to remove key from metadata") - } - } - }() ctr, err := hcsshim.CreateContainer(nsid, conf) if err != nil { @@ -301,6 +289,7 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec hyperV: spec.Windows.HyperV != nil, publisher: r.publisher, rwLayer: conf.LayerFolderPath, + rootfs: rootfs, pidPool: r.pidPool, hcsContainer: ctr, terminateDuration: createOpts.TerminateDuration, @@ -312,14 +301,6 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec } r.tasks.Add(ctx, t) - var rootfs []*containerdtypes.Mount - for _, l := range append([]string{t.rwLayer}, spec.Windows.LayerFolders...) { - rootfs = append(rootfs, &containerdtypes.Mount{ - Type: "windows-layer", - Source: l, - }) - } - r.publisher.Publish(ctx, runtime.TaskCreateEventTopic, &eventstypes.TaskCreate{ @@ -330,8 +311,8 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec Stderr: io.Stderr, Terminal: io.Terminal, }, - Pid: t.pid, - Rootfs: rootfs, + Pid: t.pid, + //???Rootfs: rootfs, // TODO: what should be in Bundle for windows? }) @@ -360,34 +341,10 @@ func (r *windowsRuntime) cleanup(ctx context.Context) { container.Wait() } container.Close() - - // TODO: remove this once we have a windows snapshotter - var layerFolderPath string - if err := r.db.View(func(tx *bolt.Tx) error { - s := newLayerFolderStore(tx) - l, e := s.Get(p.ID) - if err == nil { - layerFolderPath = l - } - return e - }); err == nil && layerFolderPath != "" { - removeLayer(ctx, layerFolderPath) - if dbErr := r.db.Update(func(tx *bolt.Tx) error { - s := newLayerFolderStore(tx) - return s.Delete(p.ID) - }); dbErr != nil { - log.G(ctx).WithField("id", p.ID). - Error("failed to remove key from metadata") - } - } else { - log.G(ctx).WithField("id", p.ID). - Debug("key not found in metadata, R/W layer may be leaked") - } - } } -func (r *windowsRuntime) serviceTask(ctx context.Context, namespace, id string, spec *specs.Spec) { +func (r *windowsRuntime) serviceTask(ctx context.Context, namespace, id string, rootfs []mount.Mount, spec *specs.Spec) { var ( err error t *task @@ -397,7 +354,7 @@ func (r *windowsRuntime) serviceTask(ctx context.Context, namespace, id string, } ) - t, err = r.newTask(ctx, namespace, id, spec, io, createOpts) + t, err = r.newTask(ctx, namespace, id, rootfs, spec, io, createOpts) if err != nil { log.G(ctx).WithError(err).WithField("id", id). Warn("failed to created servicing task") diff --git a/windows/task.go b/windows/task.go index 89bce1694..950f2e966 100644 --- a/windows/task.go +++ b/windows/task.go @@ -11,6 +11,7 @@ import ( eventstypes "github.com/containerd/containerd/api/events" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/events" + "github.com/containerd/containerd/mount" "github.com/containerd/containerd/runtime" "github.com/containerd/containerd/windows/hcsshimtypes" "github.com/containerd/typeurl" @@ -33,6 +34,7 @@ type task struct { publisher events.Publisher rwLayer string + rootfs []mount.Mount pidPool *pidPool hcsContainer hcsshim.Container @@ -406,6 +408,5 @@ func (t *task) cleanup() { for _, p := range t.processes { t.removeProcessNL(p.id) } - removeLayer(context.Background(), t.rwLayer) t.Unlock() } From e981cc6289697c22cdf981d0bfb22bbde0e3d781 Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Thu, 11 Jan 2018 17:09:28 -0800 Subject: [PATCH 3/6] Do not create new layer for View Signed-off-by: Darren Stahl --- snapshots/windows/windows.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/snapshots/windows/windows.go b/snapshots/windows/windows.go index 3d0951300..58469dd4f 100644 --- a/snapshots/windows/windows.go +++ b/snapshots/windows/windows.go @@ -228,7 +228,11 @@ func (s *snapshotter) Close() error { } func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount { - var roFlag string + var ( + roFlag string + source string + parentLayerPaths []string + ) if sn.Kind == snapshots.KindView { roFlag = "ro" @@ -236,14 +240,21 @@ func (s *snapshotter) mounts(sn storage.Snapshot) []mount.Mount { roFlag = "rw" } - parentLayerPaths := s.parentIDsToParentPaths(sn.ParentIDs) + 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:]) + } + // 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), + Source: source, Type: "windows-layer", Options: []string{ roFlag, @@ -270,16 +281,7 @@ func (s *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k 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: + if kind == snapshots.KindActive { parentLayerPaths := s.parentIDsToParentPaths(newSnapshot.ParentIDs) var parentPath string From acf2087db97139dde71548408895e5af4fe8e559 Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Tue, 16 Jan 2018 16:32:27 -0800 Subject: [PATCH 4/6] Error on no rootfs provided Signed-off-by: Darren Stahl --- windows/runtime.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/windows/runtime.go b/windows/runtime.go index 11423b31b..542c97363 100644 --- a/windows/runtime.go +++ b/windows/runtime.go @@ -11,6 +11,7 @@ import ( "github.com/Microsoft/hcsshim" eventstypes "github.com/containerd/containerd/api/events" + "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/events" "github.com/containerd/containerd/log" @@ -114,6 +115,10 @@ func (r *windowsRuntime) Create(ctx context.Context, id string, opts runtime.Cre if createOpts.TerminateDuration == 0 { createOpts.TerminateDuration = defaultTerminateDuration } + + if len(opts.Rootfs) == 0 { + return nil, errors.Wrap(errdefs.ErrInvalidArgument, "rootfs was not provided to container create") + } spec.Windows.LayerFolders = append(spec.Windows.LayerFolders, opts.Rootfs[0].Source) parentLayerPaths, err := opts.Rootfs[0].GetParentPaths() if err != nil { @@ -301,6 +306,15 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, root } r.tasks.Add(ctx, t) + var eventRootfs []*types.Mount + for _, m := range rootfs { + eventRootfs = append(eventRootfs, &types.Mount{ + Type: m.Type, + Source: m.Source, + Options: m.Options, + }) + } + r.publisher.Publish(ctx, runtime.TaskCreateEventTopic, &eventstypes.TaskCreate{ @@ -311,8 +325,8 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, root Stderr: io.Stderr, Terminal: io.Terminal, }, - Pid: t.pid, - //???Rootfs: rootfs, + Pid: t.pid, + Rootfs: eventRootfs, // TODO: what should be in Bundle for windows? }) From e6280a7c8229478091640e4498eb25cf8100da71 Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Tue, 16 Jan 2018 16:33:26 -0800 Subject: [PATCH 5/6] Enable integration tests on Windows using snapshotter and differ Signed-off-by: Darren Stahl --- client_test.go | 26 ++---- client_unix_test.go | 4 - client_windows_test.go | 74 +-------------- container_linux_test.go | 42 ++++----- container_test.go | 198 ++++++++++++++++------------------------ helpers_unix_test.go | 5 - helpers_windows_test.go | 14 --- sys/filesys_unix.go | 10 ++ sys/filesys_windows.go | 11 +++ 9 files changed, 129 insertions(+), 255 deletions(-) create mode 100644 sys/filesys_unix.go diff --git a/client_test.go b/client_test.go index db80a9c30..eb6d8539a 100644 --- a/client_test.go +++ b/client_test.go @@ -9,7 +9,6 @@ import ( golog "log" "os" "os/exec" - "runtime" "testing" "time" @@ -17,6 +16,7 @@ import ( "github.com/containerd/containerd/log" "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/sys" "github.com/containerd/containerd/testutil" "github.com/sirupsen/logrus" ) @@ -62,7 +62,7 @@ func TestMain(m *testing.M) { defer cancel() if !noDaemon { - os.RemoveAll(defaultRoot) + sys.ForceRemoveAll(defaultRoot) err := ctrd.start("containerd", address, []string{ "--root", defaultRoot, @@ -99,17 +99,10 @@ func TestMain(m *testing.M) { }).Info("running tests against containerd") // pull a seed image - if runtime.GOOS != "windows" { // TODO: remove once pull is supported on windows - if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil { - ctrd.Stop() - ctrd.Wait() - fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) - os.Exit(1) - } - } - - if err := platformTestSetup(client); err != nil { - fmt.Fprintln(os.Stderr, "platform test setup failed", err) + if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil { + ctrd.Stop() + ctrd.Wait() + fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String()) os.Exit(1) } @@ -132,7 +125,7 @@ func TestMain(m *testing.M) { fmt.Fprintln(os.Stderr, "failed to wait for containerd", err) } } - if err := os.RemoveAll(defaultRoot); err != nil { + if err := sys.ForceRemoveAll(defaultRoot); err != nil { fmt.Fprintln(os.Stderr, "failed to remove test root dir", err) os.Exit(1) } @@ -169,11 +162,6 @@ func TestNewClient(t *testing.T) { // All the container's tests depends on this, we need it to run first. func TestImagePull(t *testing.T) { - if runtime.GOOS == "windows" { - // TODO: remove once Windows has a snapshotter - t.Skip("Windows does not have a snapshotter yet") - } - client, err := newClient(t, address) if err != nil { t.Fatal(err) diff --git a/client_unix_test.go b/client_unix_test.go index 27fe7fab1..62cce45ab 100644 --- a/client_unix_test.go +++ b/client_unix_test.go @@ -16,10 +16,6 @@ var ( testImage string ) -func platformTestSetup(client *Client) error { - return nil -} - func init() { switch runtime.GOARCH { case "386": diff --git a/client_windows_test.go b/client_windows_test.go index 115d71497..4cb3e580f 100644 --- a/client_windows_test.go +++ b/client_windows_test.go @@ -1,88 +1,16 @@ package containerd import ( - "encoding/json" - "fmt" "os" "path/filepath" - - "github.com/pkg/errors" ) const ( defaultAddress = `\\.\pipe\containerd-containerd-test` - testImage = "docker.io/library/go:nanoserver" + testImage = "docker.io/microsoft/nanoserver:latest" ) var ( - dockerLayerFolders []string - defaultRoot = filepath.Join(os.Getenv("programfiles"), "containerd", "root-test") defaultState = filepath.Join(os.Getenv("programfiles"), "containerd", "state-test") ) - -func platformTestSetup(client *Client) error { - var ( - roots []string - layerChains = make(map[string]string) - ) - // Since we can't pull images yet, we'll piggyback on the default - // docker's images - wfPath := `C:\ProgramData\docker\windowsfilter` - wf, err := os.Open(wfPath) - if err != nil { - return errors.Wrapf(err, "failed to access docker layers @ %s", wfPath) - } - defer wf.Close() - entries, err := wf.Readdirnames(0) - if err != nil { - return errors.Wrapf(err, "failed to read %s entries", wfPath) - } - - for _, fn := range entries { - layerChainPath := filepath.Join(wfPath, fn, "layerchain.json") - lfi, err := os.Stat(layerChainPath) - switch { - case err == nil && lfi.Mode().IsRegular(): - f, err := os.OpenFile(layerChainPath, os.O_RDONLY, 0660) - if err != nil { - fmt.Fprintln(os.Stderr, - errors.Wrapf(err, "failed to open %s", layerChainPath)) - continue - } - defer f.Close() - l := make([]string, 0) - if err := json.NewDecoder(f).Decode(&l); err != nil { - fmt.Fprintln(os.Stderr, - errors.Wrapf(err, "failed to decode %s", layerChainPath)) - continue - } - switch { - case len(l) == 1: - layerChains[l[0]] = filepath.Join(wfPath, fn) - case len(l) > 1: - fmt.Fprintf(os.Stderr, "Too many entries in %s: %d", layerChainPath, len(l)) - case len(l) == 0: - roots = append(roots, filepath.Join(wfPath, fn)) - } - case os.IsNotExist(err): - // keep on going - default: - return errors.Wrapf(err, "error trying to access %s", layerChainPath) - } - } - - // They'll be 2 roots, just take the first one - l := roots[0] - dockerLayerFolders = append(dockerLayerFolders, l) - for { - l = layerChains[l] - if l == "" { - break - } - - dockerLayerFolders = append([]string{l}, dockerLayerFolders...) - } - - return nil -} diff --git a/container_linux_test.go b/container_linux_test.go index 82e3a9a1a..6dc6b6484 100644 --- a/container_linux_test.go +++ b/container_linux_test.go @@ -187,7 +187,7 @@ func TestDaemonRestart(t *testing.T) { t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "30")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "30")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -266,7 +266,7 @@ func TestContainerAttach(t *testing.T) { t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withCat()), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withCat()), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -427,8 +427,8 @@ func TestContainerUsername(t *testing.T) { // squid user in the alpine image has a uid of 31 container, err := client.NewContainer(ctx, id, - withNewSnapshot(id, image), - WithNewSpec(withImageConfig(image), oci.WithUsername("squid"), oci.WithProcessArgs("id", "-u")), + WithNewSnapshot(id, image), + WithNewSpec(oci.WithImageConfig(image), oci.WithUsername("squid"), oci.WithProcessArgs("id", "-u")), ) if err != nil { t.Fatal(err) @@ -487,7 +487,7 @@ func TestContainerAttachProcess(t *testing.T) { t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -620,8 +620,8 @@ func TestContainerUserID(t *testing.T) { // adm user in the alpine image has a uid of 3 and gid of 4. container, err := client.NewContainer(ctx, id, - withNewSnapshot(id, image), - WithNewSpec(withImageConfig(image), oci.WithUserID(3), oci.WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")), + WithNewSnapshot(id, image), + WithNewSpec(oci.WithImageConfig(image), oci.WithUserID(3), oci.WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")), ) if err != nil { t.Fatal(err) @@ -674,8 +674,8 @@ func TestContainerKillAll(t *testing.T) { } container, err := client.NewContainer(ctx, id, - withNewSnapshot(id, image), - WithNewSpec(withImageConfig(image), + WithNewSnapshot(id, image), + WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sh", "-c", "top"), oci.WithHostNamespace(specs.PIDNamespace), ), @@ -730,7 +730,7 @@ func TestShimSigkilled(t *testing.T) { if err != nil { t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image)), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image)), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -793,7 +793,7 @@ func TestDaemonRestartWithRunningShim(t *testing.T) { if err != nil { t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -877,8 +877,8 @@ func TestContainerRuntimeOptions(t *testing.T) { container, err := client.NewContainer( ctx, id, - WithNewSpec(withImageConfig(image), withExitStatus(7)), - withNewSnapshot(id, image), + WithNewSpec(oci.WithImageConfig(image), withExitStatus(7)), + WithNewSnapshot(id, image), WithRuntime("io.containerd.runtime.v1.linux", &runctypes.RuncOptions{Runtime: "no-runc"}), ) if err != nil { @@ -917,8 +917,8 @@ func TestContainerKillInitPidHost(t *testing.T) { } container, err := client.NewContainer(ctx, id, - withNewSnapshot(id, image), - WithNewSpec(withImageConfig(image), + WithNewSnapshot(id, image), + WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sh", "-c", "sleep 42; echo hi"), oci.WithHostNamespace(specs.PIDNamespace), ), @@ -1007,7 +1007,7 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) { t.Fatal(err) } - opts := []NewContainerOpts{WithNewSpec(withImageConfig(image), + opts := []NewContainerOpts{WithNewSpec(oci.WithImageConfig(image), withExitStatus(7), oci.WithUserNamespace(0, 1000, 10000), )} @@ -1081,13 +1081,11 @@ func TestTaskResize(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withExitStatus(7)), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withExitStatus(7)), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } diff --git a/container_test.go b/container_test.go index d3ed29ee7..257bcd9fb 100644 --- a/container_test.go +++ b/container_test.go @@ -97,13 +97,11 @@ func TestContainerStart(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withExitStatus(7)), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withExitStatus(7)), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -163,13 +161,11 @@ func TestContainerOutput(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("echo", expected)), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("echo", expected)), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -233,14 +229,12 @@ func TestContainerExec(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -320,14 +314,12 @@ func TestContainerLargeExecArgs(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -398,14 +390,12 @@ func TestContainerPids(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -477,14 +467,12 @@ func TestContainerCloseIO(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withCat()), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withCat()), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -535,14 +523,12 @@ func TestDeleteRunningContainer(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -592,14 +578,12 @@ func TestContainerKill(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "10")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "10")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -649,16 +633,14 @@ func TestContainerNoBinaryExists(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } container, err := client.NewContainer(ctx, id, - WithNewSpec(withImageConfig(image), oci.WithProcessArgs("nothing")), - withNewSnapshot(id, image)) + WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("nothing")), + WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -698,14 +680,12 @@ func TestContainerExecNoBinaryExists(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -765,14 +745,12 @@ func TestWaitStoppedTask(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withExitStatus(7)), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withExitStatus(7)), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -830,14 +808,12 @@ func TestWaitStoppedProcess(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -920,13 +896,11 @@ func TestTaskForceDelete(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "30")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "30")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -963,13 +937,11 @@ func TestProcessForceDelete(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "30")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "30")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -1034,18 +1006,16 @@ func TestContainerHostname(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("hostname"), oci.WithHostname(expected), ), - withNewSnapshot(id, image)) + WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -1105,14 +1075,12 @@ func TestContainerExitedAtSet(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withTrue()), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withTrue()), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -1167,14 +1135,12 @@ func TestDeleteContainerExecCreated(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -1239,15 +1205,13 @@ func TestContainerMetrics(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } container, err := client.NewContainer(ctx, id, - WithNewSpec(withImageConfig(image), oci.WithProcessArgs("sleep", "30")), - withNewSnapshot(id, image)) + WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "30")), + WithNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -1297,15 +1261,13 @@ func TestDeletedContainerMetrics(t *testing.T) { ) defer cancel() - if runtime.GOOS != "windows" { - image, err = client.GetImage(ctx, testImage) - if err != nil { - t.Fatal(err) - } + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Fatal(err) } container, err := client.NewContainer(ctx, id, - WithNewSpec(withImageConfig(image), withExitStatus(0)), - withNewSnapshot(id, image), + WithNewSpec(oci.WithImageConfig(image), withExitStatus(0)), + WithNewSnapshot(id, image), ) if err != nil { t.Fatal(err) diff --git a/helpers_unix_test.go b/helpers_unix_test.go index 51c034e10..8b028f9a1 100644 --- a/helpers_unix_test.go +++ b/helpers_unix_test.go @@ -39,8 +39,3 @@ func withExecExitStatus(s *specs.Process, es int) { func withExecArgs(s *specs.Process, args ...string) { s.Args = args } - -var ( - withNewSnapshot = WithNewSnapshot - withImageConfig = oci.WithImageConfig -) diff --git a/helpers_windows_test.go b/helpers_windows_test.go index a3b4768f6..83ecb7602 100644 --- a/helpers_windows_test.go +++ b/helpers_windows_test.go @@ -39,17 +39,3 @@ func withExecExitStatus(s *specs.Process, es int) { func withExecArgs(s *specs.Process, args ...string) { s.Args = append([]string{"powershell", "-noprofile"}, args...) } - -func withImageConfig(i Image) oci.SpecOpts { - return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { - s.Windows.LayerFolders = dockerLayerFolders - return nil - } -} - -func withNewSnapshot(id string, i Image) NewContainerOpts { - // TODO: when windows has a snapshotter remove the withNewSnapshot helper - return func(ctx context.Context, client *Client, c *containers.Container) error { - return nil - } -} diff --git a/sys/filesys_unix.go b/sys/filesys_unix.go new file mode 100644 index 000000000..ed23609c5 --- /dev/null +++ b/sys/filesys_unix.go @@ -0,0 +1,10 @@ +// +build !windows + +package sys + +import "os" + +// ForceRemoveAll on unix is just a wrapper for os.RemoveAll +func ForceRemoveAll(path string) error { + return os.RemoveAll(path) +} diff --git a/sys/filesys_windows.go b/sys/filesys_windows.go index b5ce13579..36395c556 100644 --- a/sys/filesys_windows.go +++ b/sys/filesys_windows.go @@ -11,6 +11,7 @@ import ( "unsafe" winio "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim" ) // MkdirAllWithACL is a wrapper for MkdirAll that creates a directory @@ -234,3 +235,13 @@ func syscallOpenSequential(path string, mode int, _ uint32) (fd syscall.Handle, h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0) return h, e } + +// ForceRemoveAll is the same as os.RemoveAll, but uses hcsshim.DestroyLayer in order +// to delete container layers. +func ForceRemoveAll(path string) error { + info := hcsshim.DriverInfo{ + HomeDir: filepath.Dir(path), + } + + return hcsshim.DestroyLayer(info, filepath.Base(path)) +} From 95a0b3af95dfb3465394f65009fbedc6deecaff3 Mon Sep 17 00:00:00 2001 From: Darren Stahl Date: Fri, 19 Jan 2018 12:52:35 -0800 Subject: [PATCH 6/6] Refactor checking for compressed diff type Signed-off-by: Darren Stahl --- diff/walking/differ.go | 17 ++++------------- diff/windows/windows.go | 17 ++++------------- images/image.go | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/diff/walking/differ.go b/diff/walking/differ.go index eb6c2df70..f1dbf43f5 100644 --- a/diff/walking/differ.go +++ b/diff/walking/differ.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "io/ioutil" - "strings" "time" "github.com/containerd/containerd/archive" @@ -75,18 +74,10 @@ func (s *walkingDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts }).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) - } + + isCompressed, err := images.IsCompressedDiff(ctx, desc.MediaType) + if err != nil { + return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", desc.MediaType) } var ocidesc ocispec.Descriptor diff --git a/diff/windows/windows.go b/diff/windows/windows.go index b288027c9..0bb79e15e 100644 --- a/diff/windows/windows.go +++ b/diff/windows/windows.go @@ -5,7 +5,6 @@ package windows import ( "io" "io/ioutil" - "strings" "time" winio "github.com/Microsoft/go-winio" @@ -74,18 +73,10 @@ func (s *windowsDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts }).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) - } + + isCompressed, err := images.IsCompressedDiff(ctx, desc.MediaType) + if err != nil { + return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", desc.MediaType) } ra, err := s.store.ReaderAt(ctx, desc.Digest) diff --git a/images/image.go b/images/image.go index 3f58e0620..5fea0dcfc 100644 --- a/images/image.go +++ b/images/image.go @@ -3,6 +3,7 @@ package images import ( "context" "encoding/json" + "strings" "time" "github.com/containerd/containerd/content" @@ -359,3 +360,22 @@ func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.D } return config.RootFS.DiffIDs, nil } + +// IsCompressedDiff returns true if mediaType is a known compressed diff media type. +// It returns false if the media type is a diff, but not compressed. If the media type +// is not a known diff type, it returns errdefs.ErrNotImplemented +func IsCompressedDiff(ctx context.Context, mediaType string) (bool, error) { + switch mediaType { + case ocispec.MediaTypeImageLayer, MediaTypeDockerSchema2Layer: + case ocispec.MediaTypeImageLayerGzip, MediaTypeDockerSchema2LayerGzip: + return true, nil + default: + // Still apply all generic media types *.tar[.+]gzip and *.tar + if strings.HasSuffix(mediaType, ".tar.gzip") || strings.HasSuffix(mediaType, ".tar+gzip") { + return true, nil + } else if !strings.HasSuffix(mediaType, ".tar") { + return false, errdefs.ErrNotImplemented + } + } + return false, nil +}