Merge pull request #1969 from darrenstahlmsft/WindowsSnapshotter5

Implement Windows snapshotter and differ
This commit is contained in:
Derek McGowan 2018-01-24 17:02:02 -08:00 committed by GitHub
commit 7e4403540d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 750 additions and 540 deletions

View File

@ -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)

View File

@ -16,10 +16,6 @@ var (
testImage string
)
func platformTestSetup(client *Client) error {
return nil
}
func init() {
switch runtime.GOARCH {
case "386":

View File

@ -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
}

View File

@ -1,6 +1,7 @@
package main
import (
_ "github.com/containerd/containerd/diff/windows"
_ "github.com/containerd/containerd/snapshots/windows"
_ "github.com/containerd/containerd/windows"
)

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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
View 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
}

View File

@ -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
)

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -0,0 +1,7 @@
// +build !windows
package diff
var defaultDifferConfig = &config{
Order: []string{"walking"},
}

View File

@ -0,0 +1,7 @@
// +build windows
package diff
var defaultDifferConfig = &config{
Order: []string{"windows"},
}

View File

@ -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
View 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)
}

View File

@ -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))
}

View File

@ -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,

View File

@ -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
}

View File

@ -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")

View File

@ -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()
}