devmapper: add pool device manager
Signed-off-by: Maksym Pavlenko <makpav@amazon.com>
This commit is contained in:
parent
6e0ae68e17
commit
3a75882520
321
snapshots/devmapper/pool_device.go
Normal file
321
snapshots/devmapper/pool_device.go
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
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 devmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// PoolDevice ties together data and metadata volumes, represents thin-pool and manages volumes, snapshots and device ids.
|
||||
type PoolDevice struct {
|
||||
poolName string
|
||||
metadata *PoolMetadata
|
||||
}
|
||||
|
||||
// NewPoolDevice creates new thin-pool from existing data and metadata volumes.
|
||||
// If pool 'poolName' already exists, it'll be reloaded with new parameters.
|
||||
func NewPoolDevice(ctx context.Context, config *Config) (*PoolDevice, error) {
|
||||
log.G(ctx).Infof("initializing pool device %q", config.PoolName)
|
||||
|
||||
version, err := dmsetup.Version()
|
||||
if err != nil {
|
||||
log.G(ctx).Errorf("dmsetup not available")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.G(ctx).Infof("using dmsetup:\n%s", version)
|
||||
|
||||
dbpath := filepath.Join(config.RootPath, config.PoolName+".db")
|
||||
poolMetaStore, err := NewPoolMetadata(dbpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := openPool(ctx, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PoolDevice{
|
||||
poolName: config.PoolName,
|
||||
metadata: poolMetaStore,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func openPool(ctx context.Context, config *Config) error {
|
||||
if err := config.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
poolPath = dmsetup.GetFullDevicePath(config.PoolName)
|
||||
poolExists = false
|
||||
)
|
||||
|
||||
if _, err := os.Stat(poolPath); err != nil && !os.IsNotExist(err) {
|
||||
return errors.Wrapf(err, "failed to stat for %q", poolPath)
|
||||
} else if err == nil {
|
||||
poolExists = true
|
||||
}
|
||||
|
||||
// Create new pool if not exists
|
||||
if !poolExists {
|
||||
log.G(ctx).Debug("creating new pool device")
|
||||
if err := dmsetup.CreatePool(config.PoolName, config.DataDevice, config.MetadataDevice, config.DataBlockSizeSectors); err != nil {
|
||||
return errors.Wrapf(err, "failed to create thin-pool with name %q", config.PoolName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pool exists, check if it needs to be reloaded
|
||||
if config.DataDevice != "" && config.MetadataDevice != "" {
|
||||
log.G(ctx).Debugf("reloading existing pool %q", poolPath)
|
||||
if err := dmsetup.ReloadPool(config.PoolName, config.DataDevice, config.MetadataDevice, config.DataBlockSizeSectors); err != nil {
|
||||
return errors.Wrapf(err, "failed to reload pool %q", config.PoolName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// If data and meta devices are not provided, use existing pool. Query info to make sure it's OK.
|
||||
if _, err := dmsetup.Info(poolPath); err != nil {
|
||||
return errors.Wrapf(err, "failed to query info for existing pool %q", poolPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// transition invokes 'updateStateFn' callback to perform devmapper operation and reflects device state changes/errors in meta store.
|
||||
// 'tryingState' will be set before invoking callback. If callback succeeded 'successState' will be set, otherwise
|
||||
// error details will be recorded in meta store.
|
||||
func (p *PoolDevice) transition(ctx context.Context, deviceName string, tryingState DeviceState, successState DeviceState, updateStateFn func() error) error {
|
||||
// Set device to trying state
|
||||
uerr := p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error {
|
||||
deviceInfo.State = tryingState
|
||||
return nil
|
||||
})
|
||||
|
||||
if uerr != nil {
|
||||
return errors.Wrapf(uerr, "failed to set device %q state to %q", deviceName, tryingState)
|
||||
}
|
||||
|
||||
var result *multierror.Error
|
||||
|
||||
// Invoke devmapper operation
|
||||
err := updateStateFn()
|
||||
|
||||
if err != nil {
|
||||
result = multierror.Append(result, err)
|
||||
}
|
||||
|
||||
// If operation succeeded transition to success state, otherwise save error details
|
||||
uerr = p.metadata.UpdateDevice(ctx, deviceName, func(deviceInfo *DeviceInfo) error {
|
||||
if err == nil {
|
||||
deviceInfo.State = successState
|
||||
deviceInfo.Error = ""
|
||||
} else {
|
||||
deviceInfo.Error = err.Error()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if uerr != nil {
|
||||
result = multierror.Append(result, uerr)
|
||||
}
|
||||
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// CreateThinDevice creates new devmapper thin-device with given name and size.
|
||||
// Device ID for thin-device will be allocated from metadata store.
|
||||
// If allocation successful, device will be activated with /dev/mapper/<deviceName>
|
||||
func (p *PoolDevice) CreateThinDevice(ctx context.Context, deviceName string, virtualSizeBytes uint64) error {
|
||||
info := &DeviceInfo{
|
||||
Name: deviceName,
|
||||
Size: virtualSizeBytes,
|
||||
State: Unknown,
|
||||
}
|
||||
|
||||
// Save initial device metadata and allocate new device ID from store
|
||||
if err := p.metadata.AddDevice(ctx, info); err != nil {
|
||||
return errors.Wrapf(err, "failed to save initial metadata for new thin device %q", deviceName)
|
||||
}
|
||||
|
||||
// Create thin device
|
||||
if err := p.transition(ctx, deviceName, Creating, Created, func() error {
|
||||
return dmsetup.CreateDevice(p.poolName, info.DeviceID)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to create new thin device %q (dev: %d)", info.Name, info.DeviceID)
|
||||
}
|
||||
|
||||
// Activate thin device
|
||||
if err := p.transition(ctx, deviceName, Activating, Activated, func() error {
|
||||
return dmsetup.ActivateDevice(p.poolName, info.Name, info.DeviceID, info.Size, "")
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to activate new thin device %q (dev: %d)", info.Name, info.DeviceID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSnapshotDevice creates and activates new thin-device from parent thin-device (makes snapshot)
|
||||
func (p *PoolDevice) CreateSnapshotDevice(ctx context.Context, deviceName string, snapshotName string, virtualSizeBytes uint64) error {
|
||||
baseInfo, err := p.metadata.GetDevice(ctx, deviceName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to query device metadata for %q", deviceName)
|
||||
}
|
||||
|
||||
isActivated := baseInfo.State == Activated
|
||||
|
||||
// Suspend thin device if it was activated previously
|
||||
if isActivated {
|
||||
if err := p.transition(ctx, baseInfo.Name, Suspending, Suspended, func() error {
|
||||
return dmsetup.SuspendDevice(baseInfo.Name)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to suspend device %q", baseInfo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
snapInfo := &DeviceInfo{
|
||||
Name: snapshotName,
|
||||
Size: virtualSizeBytes,
|
||||
ParentName: deviceName,
|
||||
State: Unknown,
|
||||
}
|
||||
|
||||
// Save snapshot metadata and allocate new device ID
|
||||
if err := p.metadata.AddDevice(ctx, snapInfo); err != nil {
|
||||
return errors.Wrapf(err, "failed to save initial metadata for snapshot %q", snapshotName)
|
||||
}
|
||||
|
||||
// Create thin device snapshot
|
||||
if err := p.transition(ctx, snapInfo.Name, Creating, Created, func() error {
|
||||
return dmsetup.CreateSnapshot(p.poolName, snapInfo.DeviceID, baseInfo.DeviceID)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err,
|
||||
"failed to create snapshot %q (dev: %d) from %q (dev: %d, activated: %t)",
|
||||
snapInfo.Name,
|
||||
snapInfo.DeviceID,
|
||||
baseInfo.Name,
|
||||
baseInfo.DeviceID,
|
||||
isActivated)
|
||||
}
|
||||
|
||||
if isActivated {
|
||||
// Resume base thin-device
|
||||
if err := p.transition(ctx, baseInfo.Name, Resuming, Resumed, func() error {
|
||||
return dmsetup.ResumeDevice(baseInfo.Name)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to resume device %q", deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
// Activate snapshot
|
||||
if err := p.transition(ctx, snapInfo.Name, Activating, Activated, func() error {
|
||||
return dmsetup.ActivateDevice(p.poolName, snapInfo.Name, snapInfo.DeviceID, snapInfo.Size, "")
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to activate snapshot device %q (dev: %d)", snapInfo.Name, snapInfo.DeviceID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeactivateDevice deactivates thin device
|
||||
func (p *PoolDevice) DeactivateDevice(ctx context.Context, deviceName string, deferred bool) error {
|
||||
devicePath := dmsetup.GetFullDevicePath(deviceName)
|
||||
if _, err := os.Stat(devicePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
opts := []dmsetup.RemoveDeviceOpt{dmsetup.RemoveWithForce, dmsetup.RemoveWithRetries}
|
||||
if deferred {
|
||||
opts = append(opts, dmsetup.RemoveDeferred)
|
||||
}
|
||||
|
||||
if err := p.transition(ctx, deviceName, Deactivating, Deactivated, func() error {
|
||||
return dmsetup.RemoveDevice(deviceName, opts...)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to deactivate device %q", deviceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveDevice completely wipes out thin device from thin-pool and frees it's device ID
|
||||
func (p *PoolDevice) RemoveDevice(ctx context.Context, deviceName string) error {
|
||||
info, err := p.metadata.GetDevice(ctx, deviceName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "can't query metadata for device %q", deviceName)
|
||||
}
|
||||
|
||||
if err := p.DeactivateDevice(ctx, deviceName, true); err != nil && err != ErrNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.transition(ctx, deviceName, Removing, Removed, func() error {
|
||||
// Send 'delete' message to thin-pool
|
||||
return dmsetup.DeleteDevice(p.poolName, info.DeviceID)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete device %q (dev id: %d)", info.Name, info.DeviceID)
|
||||
}
|
||||
|
||||
// Remove record from meta store and free device ID
|
||||
if err := p.metadata.RemoveDevice(ctx, deviceName); err != nil {
|
||||
return errors.Wrapf(err, "can't remove device %q metadata from store after removal", deviceName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePool deactivates all child thin-devices and removes thin-pool device
|
||||
func (p *PoolDevice) RemovePool(ctx context.Context) error {
|
||||
deviceNames, err := p.metadata.GetDeviceNames(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't query device names")
|
||||
}
|
||||
|
||||
var result *multierror.Error
|
||||
|
||||
// Deactivate devices if any
|
||||
for _, name := range deviceNames {
|
||||
if err := p.DeactivateDevice(ctx, name, true); err != nil && err != ErrNotFound {
|
||||
result = multierror.Append(result, errors.Wrapf(err, "failed to remove %q", name))
|
||||
}
|
||||
}
|
||||
|
||||
if err := dmsetup.RemoveDevice(p.poolName, dmsetup.RemoveWithForce, dmsetup.RemoveWithRetries, dmsetup.RemoveDeferred); err != nil {
|
||||
result = multierror.Append(result, errors.Wrapf(err, "failed to remove pool %q", p.poolName))
|
||||
}
|
||||
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
// Close closes pool device (thin-pool will not be removed)
|
||||
func (p *PoolDevice) Close() error {
|
||||
return p.metadata.Close()
|
||||
}
|
231
snapshots/devmapper/pool_device_test.go
Normal file
231
snapshots/devmapper/pool_device_test.go
Normal file
@ -0,0 +1,231 @@
|
||||
/*
|
||||
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 devmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/containerd/pkg/testutil"
|
||||
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
|
||||
"github.com/containerd/containerd/snapshots/devmapper/losetup"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
thinDevice1 = "thin-1"
|
||||
thinDevice2 = "thin-2"
|
||||
snapDevice1 = "snap-1"
|
||||
device1Size = 100000
|
||||
device2Size = 200000
|
||||
testsPrefix = "devmapper-snapshotter-tests-"
|
||||
)
|
||||
|
||||
// TestPoolDevice runs integration tests for pool device.
|
||||
// The following scenario implemented:
|
||||
// - Create pool device with name 'test-pool-device'
|
||||
// - Create two thin volumes 'thin-1' and 'thin-2'
|
||||
// - Write ext4 file system on 'thin-1' and make sure it'errs moutable
|
||||
// - Write v1 test file on 'thin-1' volume
|
||||
// - Take 'thin-1' snapshot 'snap-1'
|
||||
// - Change v1 file to v2 on 'thin-1'
|
||||
// - Mount 'snap-1' and make sure test file is v1
|
||||
// - Unmount volumes and remove all devices
|
||||
func TestPoolDevice(t *testing.T) {
|
||||
testutil.RequiresRoot(t)
|
||||
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
ctx := context.Background()
|
||||
|
||||
tempDir, err := ioutil.TempDir("", "pool-device-test-")
|
||||
assert.NilError(t, err, "couldn't get temp directory for testing")
|
||||
|
||||
_, loopDataDevice := createLoopbackDevice(t, tempDir)
|
||||
_, loopMetaDevice := createLoopbackDevice(t, tempDir)
|
||||
|
||||
defer func() {
|
||||
// Detach loop devices and remove images
|
||||
err := losetup.DetachLoopDevice(loopDataDevice, loopMetaDevice)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = os.RemoveAll(tempDir)
|
||||
assert.NilError(t, err, "couldn't cleanup temp directory")
|
||||
}()
|
||||
|
||||
config := &Config{
|
||||
PoolName: "test-pool-device-1",
|
||||
RootPath: tempDir,
|
||||
DataDevice: loopDataDevice,
|
||||
MetadataDevice: loopMetaDevice,
|
||||
DataBlockSize: "65536",
|
||||
DataBlockSizeSectors: 128,
|
||||
BaseImageSize: "16mb",
|
||||
BaseImageSizeBytes: 16 * 1024 * 1024,
|
||||
}
|
||||
|
||||
pool, err := NewPoolDevice(ctx, config)
|
||||
assert.NilError(t, err, "can't create device pool")
|
||||
assert.Assert(t, pool != nil)
|
||||
|
||||
defer func() {
|
||||
err := pool.RemovePool(ctx)
|
||||
assert.NilError(t, err, "can't close device pool")
|
||||
}()
|
||||
|
||||
// Create thin devices
|
||||
t.Run("CreateThinDevice", func(t *testing.T) {
|
||||
testCreateThinDevice(t, pool)
|
||||
})
|
||||
|
||||
// Make ext4 filesystem on 'thin-1'
|
||||
t.Run("MakeFileSystem", func(t *testing.T) {
|
||||
testMakeFileSystem(t, pool)
|
||||
})
|
||||
|
||||
// Mount 'thin-1'
|
||||
thin1MountPath := tempMountPath(t)
|
||||
output, err := exec.Command("mount", dmsetup.GetFullDevicePath(thinDevice1), thin1MountPath).CombinedOutput()
|
||||
assert.NilError(t, err, "failed to mount '%s': %s", thinDevice1, string(output))
|
||||
|
||||
// Write v1 test file on 'thin-1' device
|
||||
thin1TestFilePath := filepath.Join(thin1MountPath, "TEST")
|
||||
err = ioutil.WriteFile(thin1TestFilePath, []byte("test file (v1)"), 0700)
|
||||
assert.NilError(t, err, "failed to write test file v1 on '%s' volume", thinDevice1)
|
||||
|
||||
// Take snapshot of 'thin-1'
|
||||
t.Run("CreateSnapshotDevice", func(t *testing.T) {
|
||||
testCreateSnapshot(t, pool)
|
||||
})
|
||||
|
||||
// Update TEST file on 'thin-1' to v2
|
||||
err = ioutil.WriteFile(thin1TestFilePath, []byte("test file (v2)"), 0700)
|
||||
assert.NilError(t, err, "failed to write test file v2 on 'thin-1' volume after taking snapshot")
|
||||
|
||||
// Mount 'snap-1' and make sure TEST file is v1
|
||||
snap1MountPath := tempMountPath(t)
|
||||
output, err = exec.Command("mount", dmsetup.GetFullDevicePath(snapDevice1), snap1MountPath).CombinedOutput()
|
||||
assert.NilError(t, err, "failed to mount '%s' device: %s", snapDevice1, string(output))
|
||||
|
||||
// Read test file from snapshot device and make sure it's v1
|
||||
fileData, err := ioutil.ReadFile(filepath.Join(snap1MountPath, "TEST"))
|
||||
assert.NilError(t, err, "couldn't read test file from '%s' device", snapDevice1)
|
||||
assert.Assert(t, string(fileData) == "test file (v1)", "test file content is invalid on snapshot")
|
||||
|
||||
// Unmount devices before removing
|
||||
output, err = exec.Command("umount", thin1MountPath, snap1MountPath).CombinedOutput()
|
||||
assert.NilError(t, err, "failed to unmount devices: %s", string(output))
|
||||
|
||||
t.Run("DeactivateDevice", func(t *testing.T) {
|
||||
testDeactivateThinDevice(t, pool)
|
||||
})
|
||||
|
||||
t.Run("RemoveDevice", func(t *testing.T) {
|
||||
testRemoveThinDevice(t, pool)
|
||||
})
|
||||
}
|
||||
|
||||
func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := pool.CreateThinDevice(ctx, thinDevice1, device1Size)
|
||||
assert.NilError(t, err, "can't create first thin device")
|
||||
|
||||
err = pool.CreateThinDevice(ctx, thinDevice1, device1Size)
|
||||
assert.Assert(t, err != nil, "device pool allows duplicated device names")
|
||||
|
||||
err = pool.CreateThinDevice(ctx, thinDevice2, device2Size)
|
||||
assert.NilError(t, err, "can't create second thin device")
|
||||
|
||||
deviceInfo1, err := pool.metadata.GetDevice(ctx, thinDevice1)
|
||||
assert.NilError(t, err)
|
||||
|
||||
deviceInfo2, err := pool.metadata.GetDevice(ctx, thinDevice2)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Assert(t, deviceInfo1.DeviceID != deviceInfo2.DeviceID, "assigned device ids should be different")
|
||||
}
|
||||
|
||||
func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
|
||||
devicePath := dmsetup.GetFullDevicePath(thinDevice1)
|
||||
args := []string{
|
||||
devicePath,
|
||||
"-E",
|
||||
"nodiscard,lazy_itable_init=0,lazy_journal_init=0",
|
||||
}
|
||||
|
||||
output, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
|
||||
assert.NilError(t, err, "failed to make filesystem on '%s': %s", thinDevice1, string(output))
|
||||
}
|
||||
|
||||
func testCreateSnapshot(t *testing.T, pool *PoolDevice) {
|
||||
err := pool.CreateSnapshotDevice(context.Background(), thinDevice1, snapDevice1, device1Size)
|
||||
assert.NilError(t, err, "failed to create snapshot from '%s' volume", thinDevice1)
|
||||
}
|
||||
|
||||
func testDeactivateThinDevice(t *testing.T, pool *PoolDevice) {
|
||||
deviceList := []string{
|
||||
thinDevice2,
|
||||
snapDevice1,
|
||||
}
|
||||
|
||||
for _, deviceName := range deviceList {
|
||||
err := pool.DeactivateDevice(context.Background(), deviceName, false)
|
||||
assert.NilError(t, err, "failed to remove '%s'", deviceName)
|
||||
}
|
||||
|
||||
err := pool.DeactivateDevice(context.Background(), "not-existing-device", false)
|
||||
assert.Assert(t, err != nil, "should return an error if trying to remove not existing device")
|
||||
}
|
||||
|
||||
func testRemoveThinDevice(t *testing.T, pool *PoolDevice) {
|
||||
err := pool.RemoveDevice(testCtx, thinDevice1)
|
||||
assert.NilError(t, err, "should delete thin device from pool")
|
||||
}
|
||||
|
||||
func tempMountPath(t *testing.T) string {
|
||||
path, err := ioutil.TempDir("", "devmapper-snapshotter-mount-")
|
||||
assert.NilError(t, err, "failed to get temp directory for mount")
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func createLoopbackDevice(t *testing.T, dir string) (string, string) {
|
||||
file, err := ioutil.TempFile(dir, testsPrefix)
|
||||
assert.NilError(t, err)
|
||||
|
||||
size, err := units.RAMInBytes("128Mb")
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = file.Truncate(size)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = file.Close()
|
||||
assert.NilError(t, err)
|
||||
|
||||
imagePath := file.Name()
|
||||
|
||||
loopDevice, err := losetup.AttachLoopDevice(imagePath)
|
||||
assert.NilError(t, err)
|
||||
|
||||
return imagePath, loopDevice
|
||||
}
|
Loading…
Reference in New Issue
Block a user