mirror of
https://github.com/moby/moby.git
synced 2026-07-13 02:52:20 +00:00
It has been pointed out that sometimes device mapper unit tests fail with the following diagnostics: > --- FAIL: TestDevmapperSetup (0.02s) > graphtest_unix.go:44: graphdriver: loopback attach failed > graphtest_unix.go:48: loopback attach failed The root cause is the absence of udev inside the container used for testing, which causes device nodes (/dev/loop*) to not be created. The test suite itself already has a workaround, but it only creates 8 devices (loop0 till loop7). It might very well be the case that the first few devices are already used by the system (on my laptop 15 devices are busy). The fix is to raise the number of devices being manually created. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
207 lines
6.5 KiB
Go
207 lines
6.5 KiB
Go
// +build linux
|
|
|
|
package devmapper // import "github.com/docker/docker/daemon/graphdriver/devmapper"
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/docker/daemon/graphdriver"
|
|
"github.com/docker/docker/daemon/graphdriver/graphtest"
|
|
"github.com/docker/docker/pkg/parsers/kernel"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func init() {
|
|
// Reduce the size of the base fs and loopback for the tests
|
|
defaultDataLoopbackSize = 300 * 1024 * 1024
|
|
defaultMetaDataLoopbackSize = 200 * 1024 * 1024
|
|
defaultBaseFsSize = 300 * 1024 * 1024
|
|
defaultUdevSyncOverride = true
|
|
if err := initLoopbacks(); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// initLoopbacks ensures that the loopback devices are properly created within
|
|
// the system running the device mapper tests.
|
|
func initLoopbacks() error {
|
|
statT, err := getBaseLoopStats()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// create at least 128 loopback files, since a few first ones
|
|
// might be already in use by the host OS
|
|
for i := 0; i < 128; i++ {
|
|
loopPath := fmt.Sprintf("/dev/loop%d", i)
|
|
// only create new loopback files if they don't exist
|
|
if _, err := os.Stat(loopPath); err != nil {
|
|
if mkerr := syscall.Mknod(loopPath,
|
|
uint32(statT.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil { // nolint: unconvert
|
|
return mkerr
|
|
}
|
|
os.Chown(loopPath, int(statT.Uid), int(statT.Gid))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getBaseLoopStats inspects /dev/loop0 to collect uid,gid, and mode for the
|
|
// loop0 device on the system. If it does not exist we assume 0,0,0660 for the
|
|
// stat data
|
|
func getBaseLoopStats() (*syscall.Stat_t, error) {
|
|
loop0, err := os.Stat("/dev/loop0")
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return &syscall.Stat_t{
|
|
Uid: 0,
|
|
Gid: 0,
|
|
Mode: 0660,
|
|
}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return loop0.Sys().(*syscall.Stat_t), nil
|
|
}
|
|
|
|
// This avoids creating a new driver for each test if all tests are run
|
|
// Make sure to put new tests between TestDevmapperSetup and TestDevmapperTeardown
|
|
func TestDevmapperSetup(t *testing.T) {
|
|
graphtest.GetDriver(t, "devicemapper")
|
|
}
|
|
|
|
func TestDevmapperCreateEmpty(t *testing.T) {
|
|
graphtest.DriverTestCreateEmpty(t, "devicemapper")
|
|
}
|
|
|
|
func TestDevmapperCreateBase(t *testing.T) {
|
|
graphtest.DriverTestCreateBase(t, "devicemapper")
|
|
}
|
|
|
|
func TestDevmapperCreateSnap(t *testing.T) {
|
|
graphtest.DriverTestCreateSnap(t, "devicemapper")
|
|
}
|
|
|
|
func TestDevmapperTeardown(t *testing.T) {
|
|
graphtest.PutDriver(t)
|
|
}
|
|
|
|
func TestDevmapperReduceLoopBackSize(t *testing.T) {
|
|
tenMB := int64(10 * 1024 * 1024)
|
|
testChangeLoopBackSize(t, -tenMB, defaultDataLoopbackSize, defaultMetaDataLoopbackSize)
|
|
}
|
|
|
|
func TestDevmapperIncreaseLoopBackSize(t *testing.T) {
|
|
tenMB := int64(10 * 1024 * 1024)
|
|
testChangeLoopBackSize(t, tenMB, defaultDataLoopbackSize+tenMB, defaultMetaDataLoopbackSize+tenMB)
|
|
}
|
|
|
|
func testChangeLoopBackSize(t *testing.T, delta, expectDataSize, expectMetaDataSize int64) {
|
|
driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
|
defer graphtest.PutDriver(t)
|
|
// make sure data or metadata loopback size are the default size
|
|
if s := driver.DeviceSet.Status(); s.Data.Total != uint64(defaultDataLoopbackSize) || s.Metadata.Total != uint64(defaultMetaDataLoopbackSize) {
|
|
t.Fatal("data or metadata loop back size is incorrect")
|
|
}
|
|
if err := driver.Cleanup(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
//Reload
|
|
d, err := Init(driver.home, []string{
|
|
fmt.Sprintf("dm.loopdatasize=%d", defaultDataLoopbackSize+delta),
|
|
fmt.Sprintf("dm.loopmetadatasize=%d", defaultMetaDataLoopbackSize+delta),
|
|
}, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("error creating devicemapper driver: %v", err)
|
|
}
|
|
driver = d.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
|
if s := driver.DeviceSet.Status(); s.Data.Total != uint64(expectDataSize) || s.Metadata.Total != uint64(expectMetaDataSize) {
|
|
t.Fatal("data or metadata loop back size is incorrect")
|
|
}
|
|
if err := driver.Cleanup(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Make sure devices.Lock() has been release upon return from cleanupDeletedDevices() function
|
|
func TestDevmapperLockReleasedDeviceDeletion(t *testing.T) {
|
|
driver := graphtest.GetDriver(t, "devicemapper").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
|
defer graphtest.PutDriver(t)
|
|
|
|
// Call cleanupDeletedDevices() and after the call take and release
|
|
// DeviceSet Lock. If lock has not been released, this will hang.
|
|
driver.DeviceSet.cleanupDeletedDevices()
|
|
|
|
doneChan := make(chan bool)
|
|
|
|
go func() {
|
|
driver.DeviceSet.Lock()
|
|
defer driver.DeviceSet.Unlock()
|
|
doneChan <- true
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(time.Second * 5):
|
|
// Timer expired. That means lock was not released upon
|
|
// function return and we are deadlocked. Release lock
|
|
// here so that cleanup could succeed and fail the test.
|
|
driver.DeviceSet.Unlock()
|
|
t.Fatal("Could not acquire devices lock after call to cleanupDeletedDevices()")
|
|
case <-doneChan:
|
|
}
|
|
}
|
|
|
|
// Ensure that mounts aren't leakedriver. It's non-trivial for us to test the full
|
|
// reproducer of #34573 in a unit test, but we can at least make sure that a
|
|
// simple command run in a new namespace doesn't break things horribly.
|
|
func TestDevmapperMountLeaks(t *testing.T) {
|
|
if !kernel.CheckKernelVersion(3, 18, 0) {
|
|
t.Skipf("kernel version <3.18.0 and so is missing torvalds/linux@8ed936b5671bfb33d89bc60bdcc7cf0470ba52fe.")
|
|
}
|
|
|
|
driver := graphtest.GetDriver(t, "devicemapper", "dm.use_deferred_removal=false", "dm.use_deferred_deletion=false").(*graphtest.Driver).Driver.(*graphdriver.NaiveDiffDriver).ProtoDriver.(*Driver)
|
|
defer graphtest.PutDriver(t)
|
|
|
|
// We need to create a new (dummy) device.
|
|
if err := driver.Create("some-layer", "", nil); err != nil {
|
|
t.Fatalf("setting up some-layer: %v", err)
|
|
}
|
|
|
|
// Mount the device.
|
|
_, err := driver.Get("some-layer", "")
|
|
if err != nil {
|
|
t.Fatalf("mounting some-layer: %v", err)
|
|
}
|
|
|
|
// Create a new subprocess which will inherit our mountpoint, then
|
|
// intentionally leak it and stick around. We can't do this entirely within
|
|
// Go because forking and namespaces in Go are really not handled well at
|
|
// all.
|
|
cmd := exec.Cmd{
|
|
Path: "/bin/sh",
|
|
Args: []string{
|
|
"/bin/sh", "-c",
|
|
"mount --make-rprivate / && sleep 1000s",
|
|
},
|
|
SysProcAttr: &syscall.SysProcAttr{
|
|
Unshareflags: syscall.CLONE_NEWNS,
|
|
},
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("starting sub-command: %v", err)
|
|
}
|
|
defer func() {
|
|
unix.Kill(cmd.Process.Pid, unix.SIGKILL)
|
|
cmd.Wait()
|
|
}()
|
|
|
|
// Now try to "drop" the device.
|
|
if err := driver.Put("some-layer"); err != nil {
|
|
t.Fatalf("unmounting some-layer: %v", err)
|
|
}
|
|
}
|