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 }