From 5cc915c26cdc92945f5339f31a934746f7edc2a2 Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Mon, 26 Feb 2018 20:56:16 +0900 Subject: [PATCH] overlay: add Supported() checker This function is not called during plugin initialization (#2140), but should be useful for downstream projects that uses overlayfs snapshotter as a Go library. Benchmark result on Ubuntu 17.10, GCE n1-standard-4: BenchmarkOverlaySupportedOnExt4-4 100 20490598 ns/op BenchmarkOverlayUnsupportedOnFType0XFS-4 30000 39316 ns/op BenchmarkOverlaySupportedOnFType1XFS-4 100 19287083 ns/op BenchmarkOverlayUnsupportedOnFAT-4 100 14217772 ns/op i.e. the overhead is typically about 20 msec on this machine. Signed-off-by: Akihiro Suda --- snapshots/overlay/check.go | 88 ++++++++++++++++++++++++++++++++ snapshots/overlay/check_test.go | 89 +++++++++++++++++++++++++++++++++ testutil/helpers_unix.go | 2 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 snapshots/overlay/check.go create mode 100644 snapshots/overlay/check_test.go diff --git a/snapshots/overlay/check.go b/snapshots/overlay/check.go new file mode 100644 index 000000000..6ec59b7ed --- /dev/null +++ b/snapshots/overlay/check.go @@ -0,0 +1,88 @@ +// +build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package overlay + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/containerd/containerd/log" + "github.com/containerd/containerd/mount" + "github.com/containerd/continuity/fs" + "github.com/pkg/errors" +) + +// supportsMultipleLowerDir checks if the system supports multiple lowerdirs, +// which is required for the overlay snapshotter. On 4.x kernels, multiple lowerdirs +// are always available (so this check isn't needed), and backported to RHEL and +// CentOS 3.x kernels (3.10.0-693.el7.x86_64 and up). This function is to detect +// support on those kernels, without doing a kernel version compare. +// +// Ported from moby overlay2. +func supportsMultipleLowerDir(d string) error { + td, err := ioutil.TempDir(d, "multiple-lowerdir-check") + if err != nil { + return err + } + defer func() { + if err := os.RemoveAll(td); err != nil { + log.L.WithError(err).Warnf("Failed to remove check directory %v", td) + } + }() + + for _, dir := range []string{"lower1", "lower2", "upper", "work", "merged"} { + if err := os.Mkdir(filepath.Join(td, dir), 0755); err != nil { + return err + } + } + + opts := fmt.Sprintf("lowerdir=%s:%s,upperdir=%s,workdir=%s", filepath.Join(td, "lower2"), filepath.Join(td, "lower1"), filepath.Join(td, "upper"), filepath.Join(td, "work")) + m := mount.Mount{ + Type: "overlay", + Source: "overlay", + Options: []string{opts}, + } + dest := filepath.Join(td, "merged") + if err := m.Mount(dest); err != nil { + return errors.Wrap(err, "failed to mount overlay") + } + if err := mount.UnmountAll(dest, 0); err != nil { + log.L.WithError(err).Warnf("Failed to unmount check directory %v", dest) + } + return nil +} + +// Supported returns nil when the overlayfs is functional on the system with the root directory. +// Suppported is not called during plugin initialization, but exposed for downstream projects which uses +// this snapshotter as a library. +func Supported(root string) error { + if err := os.MkdirAll(root, 0700); err != nil { + return err + } + supportsDType, err := fs.SupportsDType(root) + if err != nil { + return err + } + if !supportsDType { + return fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", root) + } + return supportsMultipleLowerDir(root) +} diff --git a/snapshots/overlay/check_test.go b/snapshots/overlay/check_test.go new file mode 100644 index 000000000..ec36d800b --- /dev/null +++ b/snapshots/overlay/check_test.go @@ -0,0 +1,89 @@ +// +build linux + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package overlay + +import ( + "io/ioutil" + "os" + "os/exec" + "testing" + + "github.com/containerd/containerd/testutil" +) + +func testOverlaySupported(t testing.TB, expected bool, mkfs ...string) { + testutil.RequiresRoot(t) + mnt, err := ioutil.TempDir("", "containerd-fs-test-supports-overlay") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(mnt) + + deviceName, cleanupDevice, err := testutil.NewLoopback(100 << 20) // 100 MB + if err != nil { + t.Fatal(err) + } + if out, err := exec.Command(mkfs[0], append(mkfs[1:], deviceName)...).CombinedOutput(); err != nil { + // not fatal + t.Skipf("could not mkfs (%v) %s: %v (out: %q)", mkfs, deviceName, err, string(out)) + } + if out, err := exec.Command("mount", deviceName, mnt).CombinedOutput(); err != nil { + // not fatal + t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out)) + } + defer func() { + testutil.Unmount(t, mnt) + cleanupDevice() + }() + workload := func() { + err = Supported(mnt) + if expected && err != nil { + t.Fatal(err) + } + if !expected && err == nil { + t.Fatal("error is expected") + } + } + b, ok := t.(*testing.B) + if ok { + b.ResetTimer() + for i := 0; i < b.N; i++ { + workload() + } + b.StopTimer() + } else { + workload() + } +} + +func BenchmarkOverlaySupportedOnExt4(b *testing.B) { + testOverlaySupported(b, true, "mkfs.ext4", "-F") +} + +func BenchmarkOverlayUnsupportedOnFType0XFS(b *testing.B) { + testOverlaySupported(b, false, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=0") +} + +func BenchmarkOverlaySupportedOnFType1XFS(b *testing.B) { + testOverlaySupported(b, true, "mkfs.xfs", "-m", "crc=0", "-n", "ftype=1") +} + +func BenchmarkOverlayUnsupportedOnFAT(b *testing.B) { + testOverlaySupported(b, false, "mkfs.fat") +} diff --git a/testutil/helpers_unix.go b/testutil/helpers_unix.go index b4edb7d74..7f6abe496 100644 --- a/testutil/helpers_unix.go +++ b/testutil/helpers_unix.go @@ -28,7 +28,7 @@ import ( ) // Unmount unmounts a given mountPoint and sets t.Error if it fails -func Unmount(t *testing.T, mountPoint string) { +func Unmount(t testing.TB, mountPoint string) { t.Log("unmount", mountPoint) err := mount.UnmountAll(mountPoint, umountflags) assert.NilError(t, err)