github.com/demonoid81/containerd@v1.3.4/snapshots/devmapper/pool_device_test.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package devmapper
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/containerd/containerd/mount"
    32  	"github.com/containerd/containerd/pkg/testutil"
    33  	"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
    34  	"github.com/containerd/containerd/snapshots/devmapper/losetup"
    35  	"github.com/docker/go-units"
    36  	"github.com/sirupsen/logrus"
    37  	"gotest.tools/assert"
    38  )
    39  
    40  const (
    41  	thinDevice1 = "thin-1"
    42  	thinDevice2 = "thin-2"
    43  	snapDevice1 = "snap-1"
    44  	device1Size = 100000
    45  	device2Size = 200000
    46  	testsPrefix = "devmapper-snapshotter-tests-"
    47  )
    48  
    49  // TestPoolDevice runs integration tests for pool device.
    50  // The following scenario implemented:
    51  // - Create pool device with name 'test-pool-device'
    52  // - Create two thin volumes 'thin-1' and 'thin-2'
    53  // - Write ext4 file system on 'thin-1' and make sure it'errs moutable
    54  // - Write v1 test file on 'thin-1' volume
    55  // - Take 'thin-1' snapshot 'snap-1'
    56  // - Change v1 file to v2 on 'thin-1'
    57  // - Mount 'snap-1' and make sure test file is v1
    58  // - Unmount volumes and remove all devices
    59  func TestPoolDevice(t *testing.T) {
    60  	testutil.RequiresRoot(t)
    61  
    62  	logrus.SetLevel(logrus.DebugLevel)
    63  	ctx := context.Background()
    64  
    65  	tempDir, err := ioutil.TempDir("", "pool-device-test-")
    66  	assert.NilError(t, err, "couldn't get temp directory for testing")
    67  
    68  	_, loopDataDevice := createLoopbackDevice(t, tempDir)
    69  	_, loopMetaDevice := createLoopbackDevice(t, tempDir)
    70  
    71  	poolName := fmt.Sprintf("test-pool-device-%d", time.Now().Nanosecond())
    72  	err = dmsetup.CreatePool(poolName, loopDataDevice, loopMetaDevice, 64*1024/dmsetup.SectorSize)
    73  	assert.NilError(t, err, "failed to create pool %q", poolName)
    74  
    75  	defer func() {
    76  		// Detach loop devices and remove images
    77  		err := losetup.DetachLoopDevice(loopDataDevice, loopMetaDevice)
    78  		assert.NilError(t, err)
    79  
    80  		err = os.RemoveAll(tempDir)
    81  		assert.NilError(t, err, "couldn't cleanup temp directory")
    82  	}()
    83  
    84  	config := &Config{
    85  		PoolName:           poolName,
    86  		RootPath:           tempDir,
    87  		BaseImageSize:      "16mb",
    88  		BaseImageSizeBytes: 16 * 1024 * 1024,
    89  	}
    90  
    91  	pool, err := NewPoolDevice(ctx, config)
    92  	assert.NilError(t, err, "can't create device pool")
    93  	assert.Assert(t, pool != nil)
    94  
    95  	defer func() {
    96  		err := pool.RemovePool(ctx)
    97  		assert.NilError(t, err, "can't close device pool")
    98  	}()
    99  
   100  	// Create thin devices
   101  	t.Run("CreateThinDevice", func(t *testing.T) {
   102  		testCreateThinDevice(t, pool)
   103  	})
   104  
   105  	// Make ext4 filesystem on 'thin-1'
   106  	t.Run("MakeFileSystem", func(t *testing.T) {
   107  		testMakeFileSystem(t, pool)
   108  	})
   109  
   110  	// Mount 'thin-1' and write v1 test file on 'thin-1' device
   111  	err = mount.WithTempMount(ctx, getMounts(thinDevice1), func(thin1MountPath string) error {
   112  		// Write v1 test file on 'thin-1' device
   113  		thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
   114  		err := ioutil.WriteFile(thin1TestFilePath, []byte("test file (v1)"), 0700)
   115  		assert.NilError(t, err, "failed to write test file v1 on '%s' volume", thinDevice1)
   116  
   117  		return nil
   118  	})
   119  
   120  	// Take snapshot of 'thin-1'
   121  	t.Run("CreateSnapshotDevice", func(t *testing.T) {
   122  		testCreateSnapshot(t, pool)
   123  	})
   124  
   125  	// Update TEST file on 'thin-1' to v2
   126  	err = mount.WithTempMount(ctx, getMounts(thinDevice1), func(thin1MountPath string) error {
   127  		thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
   128  		err = ioutil.WriteFile(thin1TestFilePath, []byte("test file (v2)"), 0700)
   129  		assert.NilError(t, err, "failed to write test file v2 on 'thin-1' volume after taking snapshot")
   130  
   131  		return nil
   132  	})
   133  
   134  	assert.NilError(t, err)
   135  
   136  	// Mount 'snap-1' and make sure TEST file is v1
   137  	err = mount.WithTempMount(ctx, getMounts(snapDevice1), func(snap1MountPath string) error {
   138  		// Read test file from snapshot device and make sure it's v1
   139  		fileData, err := ioutil.ReadFile(filepath.Join(snap1MountPath, "TEST"))
   140  		assert.NilError(t, err, "couldn't read test file from '%s' device", snapDevice1)
   141  		assert.Equal(t, "test file (v1)", string(fileData), "test file content is invalid on snapshot")
   142  
   143  		return nil
   144  	})
   145  
   146  	assert.NilError(t, err)
   147  
   148  	t.Run("DeactivateDevice", func(t *testing.T) {
   149  		testDeactivateThinDevice(t, pool)
   150  	})
   151  
   152  	t.Run("RemoveDevice", func(t *testing.T) {
   153  		testRemoveThinDevice(t, pool)
   154  	})
   155  }
   156  
   157  func TestPoolDeviceMarkFaulty(t *testing.T) {
   158  	tempDir, store := createStore(t)
   159  	defer cleanupStore(t, tempDir, store)
   160  
   161  	err := store.AddDevice(testCtx, &DeviceInfo{Name: "1", State: Unknown})
   162  	assert.NilError(t, err)
   163  
   164  	// Note: do not use 'Activated' here because pool.ensureDeviceStates() will
   165  	// try to activate the real dm device, which will fail on a faked device.
   166  	err = store.AddDevice(testCtx, &DeviceInfo{Name: "2", State: Deactivated})
   167  	assert.NilError(t, err)
   168  
   169  	pool := &PoolDevice{metadata: store}
   170  	err = pool.ensureDeviceStates(testCtx)
   171  	assert.NilError(t, err)
   172  
   173  	called := 0
   174  	err = pool.metadata.WalkDevices(testCtx, func(info *DeviceInfo) error {
   175  		called++
   176  
   177  		switch called {
   178  		case 1:
   179  			assert.Equal(t, Faulty, info.State)
   180  			assert.Equal(t, "1", info.Name)
   181  		case 2:
   182  			assert.Equal(t, Deactivated, info.State)
   183  			assert.Equal(t, "2", info.Name)
   184  		default:
   185  			t.Error("unexpected walk call")
   186  		}
   187  
   188  		return nil
   189  	})
   190  	assert.NilError(t, err)
   191  	assert.Equal(t, 2, called)
   192  }
   193  
   194  func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
   195  	ctx := context.Background()
   196  
   197  	err := pool.CreateThinDevice(ctx, thinDevice1, device1Size)
   198  	assert.NilError(t, err, "can't create first thin device")
   199  
   200  	err = pool.CreateThinDevice(ctx, thinDevice1, device1Size)
   201  	assert.Assert(t, err != nil, "device pool allows duplicated device names")
   202  
   203  	err = pool.CreateThinDevice(ctx, thinDevice2, device2Size)
   204  	assert.NilError(t, err, "can't create second thin device")
   205  
   206  	deviceInfo1, err := pool.metadata.GetDevice(ctx, thinDevice1)
   207  	assert.NilError(t, err)
   208  
   209  	deviceInfo2, err := pool.metadata.GetDevice(ctx, thinDevice2)
   210  	assert.NilError(t, err)
   211  
   212  	assert.Assert(t, deviceInfo1.DeviceID != deviceInfo2.DeviceID, "assigned device ids should be different")
   213  
   214  	usage, err := pool.GetUsage(thinDevice1)
   215  	assert.NilError(t, err)
   216  	assert.Equal(t, usage, int64(0))
   217  }
   218  
   219  func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
   220  	devicePath := dmsetup.GetFullDevicePath(thinDevice1)
   221  	args := []string{
   222  		devicePath,
   223  		"-E",
   224  		"nodiscard,lazy_itable_init=0,lazy_journal_init=0",
   225  	}
   226  
   227  	output, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
   228  	assert.NilError(t, err, "failed to make filesystem on '%s': %s", thinDevice1, string(output))
   229  
   230  	usage, err := pool.GetUsage(thinDevice1)
   231  	assert.NilError(t, err)
   232  	assert.Assert(t, usage > 0)
   233  }
   234  
   235  func testCreateSnapshot(t *testing.T, pool *PoolDevice) {
   236  	err := pool.CreateSnapshotDevice(context.Background(), thinDevice1, snapDevice1, device1Size)
   237  	assert.NilError(t, err, "failed to create snapshot from '%s' volume", thinDevice1)
   238  }
   239  
   240  func testDeactivateThinDevice(t *testing.T, pool *PoolDevice) {
   241  	deviceList := []string{
   242  		thinDevice2,
   243  		snapDevice1,
   244  	}
   245  
   246  	for _, deviceName := range deviceList {
   247  		assert.Assert(t, pool.IsActivated(deviceName))
   248  
   249  		err := pool.DeactivateDevice(context.Background(), deviceName, false, true)
   250  		assert.NilError(t, err, "failed to remove '%s'", deviceName)
   251  
   252  		assert.Assert(t, !pool.IsActivated(deviceName))
   253  	}
   254  }
   255  
   256  func testRemoveThinDevice(t *testing.T, pool *PoolDevice) {
   257  	err := pool.RemoveDevice(testCtx, thinDevice1)
   258  	assert.NilError(t, err, "should delete thin device from pool")
   259  }
   260  
   261  func getMounts(thinDeviceName string) []mount.Mount {
   262  	return []mount.Mount{
   263  		{
   264  			Source: dmsetup.GetFullDevicePath(thinDeviceName),
   265  			Type:   "ext4",
   266  		},
   267  	}
   268  }
   269  
   270  func createLoopbackDevice(t *testing.T, dir string) (string, string) {
   271  	file, err := ioutil.TempFile(dir, testsPrefix)
   272  	assert.NilError(t, err)
   273  
   274  	size, err := units.RAMInBytes("128Mb")
   275  	assert.NilError(t, err)
   276  
   277  	err = file.Truncate(size)
   278  	assert.NilError(t, err)
   279  
   280  	err = file.Close()
   281  	assert.NilError(t, err)
   282  
   283  	imagePath := file.Name()
   284  
   285  	loopDevice, err := losetup.AttachLoopDevice(imagePath)
   286  	assert.NilError(t, err)
   287  
   288  	return imagePath, loopDevice
   289  }