diff --git a/benchmark_test.go b/benchmark_test.go index 7b2b8d513..3920f497a 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -20,7 +20,7 @@ func BenchmarkContainerCreate(b *testing.B) { b.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("true")) + spec, err := GenerateSpec(WithImageConfig(ctx, image), withTrue()) if err != nil { b.Error(err) return @@ -63,7 +63,7 @@ func BenchmarkContainerStart(b *testing.B) { b.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("true")) + spec, err := GenerateSpec(WithImageConfig(ctx, image), withTrue()) if err != nil { b.Error(err) return diff --git a/checkpoint_test.go b/checkpoint_test.go index 4e853bb41..599ac7f4b 100644 --- a/checkpoint_test.go +++ b/checkpoint_test.go @@ -1,3 +1,5 @@ +// +build !windows + package containerd import ( diff --git a/client_test.go b/client_test.go index 030f00bbe..682fc8bcd 100644 --- a/client_test.go +++ b/client_test.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/exec" + "runtime" "syscall" "testing" "time" @@ -17,11 +18,6 @@ import ( "github.com/containerd/containerd/testutil" ) -const ( - defaultRoot = "/var/lib/containerd-test" - testImage = "docker.io/library/alpine:latest" -) - var ( address string noDaemon bool @@ -29,7 +25,7 @@ var ( ) func init() { - flag.StringVar(&address, "address", "/run/containerd-test/containerd.sock", "The address to the containerd socket for use in the tests") + flag.StringVar(&address, "address", defaultAddress, "The address to the containerd socket for use in the tests") flag.BoolVar(&noDaemon, "no-daemon", false, "Do not start a dedicated daemon for the tests") flag.Parse() } @@ -57,11 +53,15 @@ func TestMain(m *testing.M) { defer cancel() if !noDaemon { + os.RemoveAll(defaultRoot) + // setup a new containerd daemon if !testing.Short cmd = exec.Command("containerd", "--root", defaultRoot, "--address", address, + "--log-level", "debug", ) + cmd.Stdout = buf cmd.Stderr = buf if err := cmd.Start(); err != nil { cmd.Wait() @@ -94,14 +94,22 @@ func TestMain(m *testing.M) { }).Info("running tests against containerd") // pull a seed image - if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil { - cmd.Process.Signal(syscall.SIGTERM) - cmd.Wait() - fmt.Fprintf(os.Stderr, "%s: %s", err, buf.String()) + if runtime.GOOS != "windows" { // TODO: remove once pull is supported on windows + if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil { + cmd.Process.Signal(syscall.SIGTERM) + cmd.Wait() + fmt.Fprintf(os.Stderr, "%s: %s", err, buf.String()) + os.Exit(1) + } + } + + if err := platformTestSetup(client); err != nil { + fmt.Fprintln(os.Stderr, "platform test setup failed", err) os.Exit(1) } + if err := client.Close(); err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, "failed to close client", err) } // run the test @@ -110,13 +118,15 @@ func TestMain(m *testing.M) { if !noDaemon { // tear down the daemon and resources created if err := cmd.Process.Signal(syscall.SIGTERM); err != nil { - fmt.Fprintln(os.Stderr, err) + if err := cmd.Process.Kill(); err != nil { + fmt.Fprintln(os.Stderr, "failed to signal containerd", err) + } } if err := cmd.Wait(); err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, "failed to wait for containerd", err) } if err := os.RemoveAll(defaultRoot); err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, "failed to remove test root dir", err) os.Exit(1) } // only print containerd logs if the test failed @@ -171,6 +181,11 @@ func TestNewClient(t *testing.T) { } func TestImagePull(t *testing.T) { + if runtime.GOOS == "windows" { + // TODO: remove once Windows has a snapshotter + t.Skip("Windows does not have a snapshotter yet") + } + client, err := newClient(t, address) if err != nil { t.Fatal(err) diff --git a/client_unix_test.go b/client_unix_test.go new file mode 100644 index 000000000..2cb6cc8ab --- /dev/null +++ b/client_unix_test.go @@ -0,0 +1,13 @@ +// +build !windows + +package containerd + +const ( + defaultRoot = "/var/lib/containerd-test" + defaultAddress = "/run/containerd-test/containerd.sock" + testImage = "docker.io/library/alpine:latest" +) + +func platformTestSetup(client *Client) error { + return nil +} diff --git a/client_windows_test.go b/client_windows_test.go new file mode 100644 index 000000000..e1073ca60 --- /dev/null +++ b/client_windows_test.go @@ -0,0 +1,87 @@ +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" +) + +var ( + dockerLayerFolders []string + + defaultRoot = filepath.Join(os.Getenv("programfiles"), "containerd", "root-test") +) + +func platformTestSetup(client *Client) error { + var ( + roots []string + layerChains = make(map[string]string) + ) + // Since we can't pull images yet, we'll piggyback on the default + // docker's images + wfPath := `C:\ProgramData\docker\windowsfilter` + wf, err := os.Open(wfPath) + if err != nil { + return errors.Wrapf(err, "failed to access docker layers @ %s", wfPath) + } + defer wf.Close() + entries, err := wf.Readdirnames(0) + if err != nil { + return errors.Wrapf(err, "failed to read %s entries", wfPath) + } + + for _, fn := range entries { + layerChainPath := filepath.Join(wfPath, fn, "layerchain.json") + lfi, err := os.Stat(layerChainPath) + switch { + case err == nil && lfi.Mode().IsRegular(): + f, err := os.OpenFile(layerChainPath, os.O_RDONLY, 0660) + if err != nil { + fmt.Fprintln(os.Stderr, + errors.Wrapf(err, "failed to open %s", layerChainPath)) + continue + } + defer f.Close() + l := make([]string, 0) + if err := json.NewDecoder(f).Decode(&l); err != nil { + fmt.Fprintln(os.Stderr, + errors.Wrapf(err, "failed to decode %s", layerChainPath)) + continue + } + switch { + case len(l) == 1: + layerChains[l[0]] = filepath.Join(wfPath, fn) + case len(l) > 1: + fmt.Fprintf(os.Stderr, "Too many entries in %s: %d", layerChainPath, len(l)) + case len(l) == 0: + roots = append(roots, filepath.Join(wfPath, fn)) + } + case os.IsNotExist(err): + // keep on going + default: + return errors.Wrapf(err, "error trying to access %s", layerChainPath) + } + } + + // They'll be 2 roots, just take the first one + l := roots[0] + dockerLayerFolders = append(dockerLayerFolders, l) + for { + l = layerChains[l] + if l == "" { + break + } + + dockerLayerFolders = append([]string{l}, dockerLayerFolders...) + } + + return nil +} diff --git a/container_linux_test.go b/container_linux_test.go new file mode 100644 index 000000000..a31c4e063 --- /dev/null +++ b/container_linux_test.go @@ -0,0 +1,100 @@ +// +build linux + +package containerd + +import ( + "syscall" + "testing" + + "github.com/containerd/cgroups" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +func TestContainerUpdate(t *testing.T) { + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + ctx, cancel = testContext() + id = t.Name() + ) + defer cancel() + + image, err := client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + spec, err := generateSpec(WithImageConfig(ctx, image), withProcessArgs("sleep", "30")) + if err != nil { + t.Error(err) + return + } + limit := int64(32 * 1024 * 1024) + spec.Linux.Resources.Memory = &specs.LinuxMemory{ + Limit: &limit, + } + container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + if err != nil { + t.Error(err) + return + } + defer container.Delete(ctx, WithRootFSDeletion) + + task, err := container.NewTask(ctx, empty()) + if err != nil { + t.Error(err) + return + } + defer task.Delete(ctx) + + statusC := make(chan uint32, 1) + go func() { + status, err := task.Wait(ctx) + if err != nil { + t.Error(err) + } + statusC <- status + }() + + // check that the task has a limit of 32mb + cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid()))) + if err != nil { + t.Error(err) + return + } + stat, err := cgroup.Stat(cgroups.IgnoreNotExist) + if err != nil { + t.Error(err) + return + } + if int64(stat.Memory.Usage.Limit) != limit { + t.Errorf("expected memory limit to be set to %d but received %d", limit, stat.Memory.Usage.Limit) + return + } + limit = 64 * 1024 * 1024 + if err := task.Update(ctx, WithResources(&specs.LinuxResources{ + Memory: &specs.LinuxMemory{ + Limit: &limit, + }, + })); err != nil { + t.Error(err) + } + // check that the task has a limit of 64mb + if stat, err = cgroup.Stat(cgroups.IgnoreNotExist); err != nil { + t.Error(err) + return + } + if int64(stat.Memory.Usage.Limit) != limit { + t.Errorf("expected memory limit to be set to %d but received %d", limit, stat.Memory.Usage.Limit) + } + if err := task.Kill(ctx, syscall.SIGKILL); err != nil { + t.Error(err) + return + } + + <-statusC +} diff --git a/container_test.go b/container_test.go index 5b768c0ef..77429081e 100644 --- a/container_test.go +++ b/container_test.go @@ -6,13 +6,16 @@ import ( "io" "io/ioutil" "os" + "runtime" + "strings" "sync" "syscall" "testing" - "github.com/containerd/cgroups" + // Register the typeurl + _ "github.com/containerd/containerd/runtime" + "github.com/containerd/containerd/errdefs" - specs "github.com/opencontainers/runtime-spec/specs-go" ) func empty() IOCreation { @@ -48,7 +51,7 @@ func TestNewContainer(t *testing.T) { } defer client.Close() - spec, err := GenerateSpec() + spec, err := generateSpec() if err != nil { t.Error(err) return @@ -84,22 +87,26 @@ func TestContainerStart(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withExitStatus(7)) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sh", "-c", "exit 7")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return @@ -151,23 +158,27 @@ func TestContainerOutput(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() expected = "kingkoye" ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("echo", expected)) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("echo", expected)) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return @@ -207,7 +218,7 @@ func TestContainerOutput(t *testing.T) { actual := stdout.String() // echo adds a new line - expected = expected + "\n" + expected = expected + newLine if actual != expected { t.Errorf("expected output %q but received %q", expected, actual) } @@ -221,22 +232,26 @@ func TestContainerExec(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return @@ -260,10 +275,7 @@ func TestContainerExec(t *testing.T) { // start an exec process without running the original container process info processSpec := spec.Process - processSpec.Args = []string{ - "sh", "-c", - "exit 6", - } + withExecExitStatus(processSpec, 6) execID := t.Name() + "_exec" process, err := task.Exec(ctx, execID, processSpec, empty()) if err != nil { @@ -305,7 +317,7 @@ func TestContainerExec(t *testing.T) { <-finished } -func TestContainerProcesses(t *testing.T) { +func TestContainerPids(t *testing.T) { client, err := newClient(t, address) if err != nil { t.Fatal(err) @@ -313,22 +325,26 @@ func TestContainerProcesses(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return @@ -383,29 +399,33 @@ func TestContainerCloseIO(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withCat()) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("cat")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithRootFSDeletion) - const expected = "hello\n" + const expected = "hello" + newLine stdout := bytes.NewBuffer(nil) r, w, err := os.Pipe() @@ -451,12 +471,25 @@ func TestContainerCloseIO(t *testing.T) { output := stdout.String() + if runtime.GOOS == "windows" { + // On windows we use more and it always adds an extra newline + // remove it here + output = strings.TrimSuffix(output, newLine) + } + if output != expected { t.Errorf("expected output %q but received %q", expected, output) } } func TestContainerAttach(t *testing.T) { + if runtime.GOOS == "windows" { + // On windows, closing the write side of the pipe closes the read + // side, sending an EOF to it and preventing reopening it. + // Hence this test will always fails on windows + t.Skip("invalid logic on windows") + } + client, err := newClient(t, address) if err != nil { t.Fatal(err) @@ -464,29 +497,33 @@ func TestContainerAttach(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withCat()) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("cat")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithRootFSDeletion) - expected := "hello\n" + expected := "hello" + newLine stdout := bytes.NewBuffer(nil) r, w, err := os.Pipe() @@ -586,22 +623,26 @@ func TestDeleteRunningContainer(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithImage(image), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return @@ -651,22 +692,26 @@ func TestContainerKill(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withCat()) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sh", "-c", "cat")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithImage(image), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return @@ -709,95 +754,6 @@ func TestContainerKill(t *testing.T) { } } -func TestContainerUpdate(t *testing.T) { - client, err := newClient(t, address) - if err != nil { - t.Fatal(err) - } - defer client.Close() - - var ( - ctx, cancel = testContext() - id = t.Name() - ) - defer cancel() - - image, err := client.GetImage(ctx, testImage) - if err != nil { - t.Error(err) - return - } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "30")) - if err != nil { - t.Error(err) - return - } - limit := int64(32 * 1024 * 1024) - spec.Linux.Resources.Memory = &specs.LinuxMemory{ - Limit: &limit, - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) - if err != nil { - t.Error(err) - return - } - defer container.Delete(ctx, WithRootFSDeletion) - - task, err := container.NewTask(ctx, empty()) - if err != nil { - t.Error(err) - return - } - defer task.Delete(ctx) - - statusC := make(chan uint32, 1) - go func() { - status, err := task.Wait(ctx) - if err != nil { - t.Error(err) - } - statusC <- status - }() - - // check that the task has a limit of 32mb - cgroup, err := cgroups.Load(cgroups.V1, cgroups.PidPath(int(task.Pid()))) - if err != nil { - t.Error(err) - return - } - stat, err := cgroup.Stat(cgroups.IgnoreNotExist) - if err != nil { - t.Error(err) - return - } - if int64(stat.Memory.Usage.Limit) != limit { - t.Errorf("expected memory limit to be set to %d but received %d", limit, stat.Memory.Usage.Limit) - return - } - limit = 64 * 1024 * 1024 - if err := task.Update(ctx, WithResources(&specs.LinuxResources{ - Memory: &specs.LinuxMemory{ - Limit: &limit, - }, - })); err != nil { - t.Error(err) - } - // check that the task has a limit of 64mb - if stat, err = cgroup.Stat(cgroups.IgnoreNotExist); err != nil { - t.Error(err) - return - } - if int64(stat.Memory.Usage.Limit) != limit { - t.Errorf("expected memory limit to be set to %d but received %d", limit, stat.Memory.Usage.Limit) - } - if err := task.Kill(ctx, syscall.SIGKILL); err != nil { - t.Error(err) - return - } - - <-statusC -} - func TestContainerNoBinaryExists(t *testing.T) { client, err := newClient(t, address) if err != nil { @@ -806,30 +762,47 @@ func TestContainerNoBinaryExists(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("nothing")) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("nothing")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return } defer container.Delete(ctx, WithRootFSDeletion) - if _, err := container.NewTask(ctx, Stdio); err == nil { - t.Error("NewTask should return an error when binary does not exist") + task, err := container.NewTask(ctx, Stdio) + switch runtime.GOOS { + case "windows": + if err != nil { + t.Errorf("failed to create task %v", err) + } + if err := task.Start(ctx); err != nil { + t.Error("task.Start() should return an error when binary does not exist") + task.Delete(ctx) + } + default: + if err == nil { + t.Error("NewTask should return an error when binary does not exist") + task.Delete(ctx) + } } } @@ -841,22 +814,26 @@ func TestContainerExecNoBinaryExists(t *testing.T) { defer client.Close() var ( + image Image ctx, cancel = testContext() id = t.Name() ) defer cancel() - image, err := client.GetImage(ctx, testImage) + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100")) if err != nil { t.Error(err) return } - spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100")) - if err != nil { - t.Error(err) - return - } - container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewRootFS(id, image)) + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewRootFS(id, image)) if err != nil { t.Error(err) return diff --git a/content/content_test.go b/content/content_test.go index b73cd61e4..3731628d1 100644 --- a/content/content_test.go +++ b/content/content_test.go @@ -258,9 +258,11 @@ func checkBlobPath(t *testing.T, cs Store, dgst digest.Digest) string { t.Fatalf("error stating blob path: %v", err) } - // ensure that only read bits are set. - if ((fi.Mode() & os.ModePerm) & 0333) != 0 { - t.Fatalf("incorrect permissions: %v", fi.Mode()) + if runtime.GOOS != "windows" { + // ensure that only read bits are set. + if ((fi.Mode() & os.ModePerm) & 0333) != 0 { + t.Fatalf("incorrect permissions: %v", fi.Mode()) + } } return path diff --git a/content/writer.go b/content/writer.go index 5fbaac2f5..91a5ed235 100644 --- a/content/writer.go +++ b/content/writer.go @@ -3,6 +3,7 @@ package content import ( "os" "path/filepath" + "runtime" "time" "github.com/containerd/containerd/errdefs" @@ -67,8 +68,12 @@ func (w *writer) Commit(size int64, expected digest.Digest) error { // only allowing reads honoring the umask on creation. // // This removes write and exec, only allowing read per the creation umask. - if err := w.fp.Chmod((fi.Mode() & os.ModePerm) &^ 0333); err != nil { - return errors.Wrap(err, "failed to change ingest file permissions") + // + // NOTE: Windows does not support this operation + if runtime.GOOS != "windows" { + if err := w.fp.Chmod((fi.Mode() & os.ModePerm) &^ 0333); err != nil { + return errors.Wrap(err, "failed to change ingest file permissions") + } } if size > 0 && size != fi.Size() { diff --git a/helpers_unix_test.go b/helpers_unix_test.go new file mode 100644 index 000000000..604caced7 --- /dev/null +++ b/helpers_unix_test.go @@ -0,0 +1,51 @@ +// +build !windows + +package containerd + +import ( + "context" + "fmt" + + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +const newLine = "\n" + +func generateSpec(opts ...SpecOpts) (*specs.Spec, error) { + return GenerateSpec(opts...) +} + +func withExitStatus(es int) SpecOpts { + return func(s *specs.Spec) error { + s.Process.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)} + return nil + } +} + +func withProcessArgs(args ...string) SpecOpts { + return WithProcessArgs(args...) +} + +func withCat() SpecOpts { + return WithProcessArgs("cat") +} + +func withTrue() SpecOpts { + return WithProcessArgs("true") +} + +func withExecExitStatus(s *specs.Process, es int) { + s.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)} +} + +func withExecArgs(s *specs.Process, args ...string) { + s.Args = args +} + +func withImageConfig(ctx context.Context, i Image) SpecOpts { + return WithImageConfig(ctx, i) +} + +func withNewRootFS(id string, i Image) NewContainerOpts { + return WithNewRootFS(id, i) +} diff --git a/helpers_windows_test.go b/helpers_windows_test.go new file mode 100644 index 000000000..d5c2bf5d6 --- /dev/null +++ b/helpers_windows_test.go @@ -0,0 +1,65 @@ +// +build windows + +package containerd + +import ( + "context" + "strconv" + + "github.com/containerd/containerd/containers" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +const newLine = "\r\n" + +func generateSpec(opts ...SpecOpts) (*specs.Spec, error) { + spec, err := GenerateSpec(opts...) + if err != nil { + return nil, err + } + + spec.Windows.LayerFolders = dockerLayerFolders + + return spec, nil +} + +func withExitStatus(es int) SpecOpts { + return func(s *specs.Spec) error { + s.Process.Args = []string{"powershell", "-noprofile", "exit", strconv.Itoa(es)} + return nil + } +} + +func withProcessArgs(args ...string) SpecOpts { + return WithProcessArgs(append([]string{"powershell", "-noprofile"}, args...)...) +} + +func withCat() SpecOpts { + return WithProcessArgs("cmd", "/c", "more") +} + +func withTrue() SpecOpts { + return WithProcessArgs("cmd", "/c") +} + +func withExecExitStatus(s *specs.Process, es int) { + s.Args = []string{"powershell", "-noprofile", "exit", strconv.Itoa(es)} +} + +func withExecArgs(s *specs.Process, args ...string) { + s.Args = append([]string{"powershell", "-noprofile"}, args...) +} + +func withImageConfig(ctx context.Context, i Image) SpecOpts { + // TODO: when windows has a snapshotter remove the withImageConfig helper + return func(s *specs.Spec) error { + return nil + } +} + +func withNewRootFS(id string, i Image) NewContainerOpts { + // TODO: when windows has a snapshotter remove the withNewRootFS helper + return func(ctx context.Context, client *Client, c *containers.Container) error { + return nil + } +} diff --git a/testutil/helpers.go b/testutil/helpers.go index 13e7cc3af..fd042a241 100644 --- a/testutil/helpers.go +++ b/testutil/helpers.go @@ -2,15 +2,11 @@ package testutil import ( "flag" - "fmt" "io/ioutil" "os" "path/filepath" "strconv" "testing" - - "github.com/containerd/containerd/mount" - "github.com/stretchr/testify/assert" ) var rootEnabled bool @@ -19,36 +15,6 @@ func init() { flag.BoolVar(&rootEnabled, "test.root", false, "enable tests that require root") } -// Unmount unmounts a given mountPoint and sets t.Error if it fails -func Unmount(t *testing.T, mountPoint string) { - t.Log("unmount", mountPoint) - if err := mount.Unmount(mountPoint, 0); err != nil { - t.Error("Could not umount", mountPoint, err) - } -} - -// RequiresRoot skips tests that require root, unless the test.root flag has -// been set -func RequiresRoot(t testing.TB) { - if !rootEnabled { - t.Skip("skipping test that requires root") - return - } - assert.Equal(t, 0, os.Getuid(), "This test must be run as root.") -} - -// RequiresRootM is similar to RequiresRoot but intended to be called from *testing.M. -func RequiresRootM() { - if !rootEnabled { - fmt.Fprintln(os.Stderr, "skipping test that requires root") - os.Exit(0) - } - if 0 != os.Getuid() { - fmt.Fprintln(os.Stderr, "This test must be run as root.") - os.Exit(1) - } -} - // DumpDir will log out all of the contents of the provided directory to // testing logger. // diff --git a/testutil/helpers_unix.go b/testutil/helpers_unix.go new file mode 100644 index 000000000..398209df1 --- /dev/null +++ b/testutil/helpers_unix.go @@ -0,0 +1,42 @@ +// +build !windows + +package testutil + +import ( + "fmt" + "os" + "testing" + + "github.com/containerd/containerd/mount" + "github.com/stretchr/testify/assert" +) + +// Unmount unmounts a given mountPoint and sets t.Error if it fails +func Unmount(t *testing.T, mountPoint string) { + t.Log("unmount", mountPoint) + if err := mount.Unmount(mountPoint, 0); err != nil { + t.Error("Could not umount", mountPoint, err) + } +} + +// RequiresRoot skips tests that require root, unless the test.root flag has +// been set +func RequiresRoot(t testing.TB) { + if !rootEnabled { + t.Skip("skipping test that requires root") + return + } + assert.Equal(t, 0, os.Getuid(), "This test must be run as root.") +} + +// RequiresRootM is similar to RequiresRoot but intended to be called from *testing.M. +func RequiresRootM() { + if !rootEnabled { + fmt.Fprintln(os.Stderr, "skipping test that requires root") + os.Exit(0) + } + if 0 != os.Getuid() { + fmt.Fprintln(os.Stderr, "This test must be run as root.") + os.Exit(1) + } +} diff --git a/testutil/helpers_windows.go b/testutil/helpers_windows.go new file mode 100644 index 000000000..34227ce0e --- /dev/null +++ b/testutil/helpers_windows.go @@ -0,0 +1,16 @@ +package testutil + +import "testing" + +// RequiresRoot does nothing on Windows +func RequiresRoot(t testing.TB) { +} + +// RequiresRootM is similar to RequiresRoot but intended to be called from *testing.M. +func RequiresRootM() { +} + +// Unmount unmounts a given mountPoint and sets t.Error if it fails +// Does nothing on Windows +func Unmount(t *testing.T, mountPoint string) { +}