Merge pull request #1969 from darrenstahlmsft/WindowsSnapshotter5
Implement Windows snapshotter and differ
This commit is contained in:
commit
7e4403540d
@ -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)
|
||||
|
@ -16,10 +16,6 @@ var (
|
||||
testImage string
|
||||
)
|
||||
|
||||
func platformTestSetup(client *Client) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
switch runtime.GOARCH {
|
||||
case "386":
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/containerd/containerd/diff/windows"
|
||||
_ "github.com/containerd/containerd/snapshots/windows"
|
||||
_ "github.com/containerd/containerd/windows"
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
160
diff/windows/windows.go
Normal file
160
diff/windows/windows.go
Normal file
@ -0,0 +1,160 @@
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"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")
|
||||
}
|
||||
}()
|
||||
|
||||
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)
|
||||
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) {
|
||||
return emptyDesc, errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,70 @@ 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)
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
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"},
|
||||
}
|
@ -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(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
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,270 @@ 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)
|
||||
return info, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
defer t.Rollback()
|
||||
|
||||
info, err = storage.UpdateInfo(ctx, info, fieldpaths...)
|
||||
if err != nil {
|
||||
return snapshots.Info{}, err
|
||||
}
|
||||
|
||||
if err := t.Commit(); err != nil {
|
||||
return snapshots.Info{}, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
defer t.Rollback()
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
defer t.Rollback()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
source string
|
||||
parentLayerPaths []string
|
||||
)
|
||||
|
||||
if sn.Kind == snapshots.KindView {
|
||||
roFlag = "ro"
|
||||
} else {
|
||||
roFlag = "rw"
|
||||
}
|
||||
|
||||
if len(sn.ParentIDs) == 0 || sn.Kind == snapshots.KindActive {
|
||||
source = s.getSnapshotDir(sn.ID)
|
||||
parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs)
|
||||
} else {
|
||||
source = s.getSnapshotDir(sn.ParentIDs[0])
|
||||
parentLayerPaths = s.parentIDsToParentPaths(sn.ParentIDs[1:])
|
||||
}
|
||||
|
||||
// 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: source,
|
||||
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
|
||||
}
|
||||
defer t.Rollback()
|
||||
|
||||
newSnapshot, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create snapshot")
|
||||
}
|
||||
|
||||
if kind == 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")
|
||||
}
|
||||
|
||||
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(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
|
||||
)
|
||||
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
|
||||
}
|
||||
|
10
sys/filesys_unix.go
Normal file
10
sys/filesys_unix.go
Normal file
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
@ -10,13 +10,12 @@ 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/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 +54,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 +63,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 +83,6 @@ type windowsRuntime struct {
|
||||
|
||||
monitor runtime.TaskMonitor
|
||||
tasks *runtime.TaskList
|
||||
db *metadata.DB
|
||||
}
|
||||
|
||||
func (r *windowsRuntime) ID() string {
|
||||
@ -125,7 +116,17 @@ func (r *windowsRuntime) Create(ctx context.Context, id string, opts runtime.Cre
|
||||
createOpts.TerminateDuration = defaultTerminateDuration
|
||||
}
|
||||
|
||||
return r.newTask(ctx, namespace, id, spec, opts.IO, createOpts)
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
spec.Windows.LayerFolders = append(spec.Windows.LayerFolders, parentLayerPaths...)
|
||||
|
||||
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 +210,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 +247,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 +266,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 +294,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,11 +306,12 @@ 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,
|
||||
var eventRootfs []*types.Mount
|
||||
for _, m := range rootfs {
|
||||
eventRootfs = append(eventRootfs, &types.Mount{
|
||||
Type: m.Type,
|
||||
Source: m.Source,
|
||||
Options: m.Options,
|
||||
})
|
||||
}
|
||||
|
||||
@ -331,7 +326,7 @@ func (r *windowsRuntime) newTask(ctx context.Context, namespace, id string, spec
|
||||
Terminal: io.Terminal,
|
||||
},
|
||||
Pid: t.pid,
|
||||
Rootfs: rootfs,
|
||||
Rootfs: eventRootfs,
|
||||
// TODO: what should be in Bundle for windows?
|
||||
})
|
||||
|
||||
@ -360,34 +355,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 +368,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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user