devmapper: implement Usage
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
This commit is contained in:
parent
010b4da36f
commit
87289a0c62
@ -21,11 +21,13 @@ package devmapper
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"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.
|
||||
@ -298,6 +300,29 @@ func (p *PoolDevice) IsActivated(deviceName string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage reports total size in bytes consumed by a thin-device.
|
||||
// It relies on the number of used blocks reported by 'dmsetup status'.
|
||||
// The output looks like:
|
||||
// device2: 0 204800 thin 17280 204799
|
||||
// Where 17280 is the number of used sectors
|
||||
func (p *PoolDevice) GetUsage(deviceName string) (int64, error) {
|
||||
status, err := dmsetup.Status(deviceName)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "can't get status for device %q", deviceName)
|
||||
}
|
||||
|
||||
if len(status.Params) == 0 {
|
||||
return 0, errors.Errorf("failed to get the number of used blocks, unexpected output from dmsetup status")
|
||||
}
|
||||
|
||||
count, err := strconv.ParseInt(status.Params[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "failed to parse status params: %q", status.Params[0])
|
||||
}
|
||||
|
||||
return count * dmsetup.SectorSize, 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)
|
||||
|
@ -168,6 +168,10 @@ func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Assert(t, deviceInfo1.DeviceID != deviceInfo2.DeviceID, "assigned device ids should be different")
|
||||
|
||||
usage, err := pool.GetUsage(thinDevice1)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, usage, int64(0))
|
||||
}
|
||||
|
||||
func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
|
||||
@ -180,6 +184,10 @@ func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
|
||||
|
||||
output, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
|
||||
assert.NilError(t, err, "failed to make filesystem on '%s': %s", thinDevice1, string(output))
|
||||
|
||||
usage, err := pool.GetUsage(thinDevice1)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, usage > 0)
|
||||
}
|
||||
|
||||
func testCreateSnapshot(t *testing.T, pool *PoolDevice) {
|
||||
|
@ -156,11 +156,46 @@ func (s *Snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpath
|
||||
return info, err
|
||||
}
|
||||
|
||||
// Usage not yet implemented
|
||||
// Usage returns the resource usage of an active or committed snapshot excluding the usage of parent snapshots.
|
||||
func (s *Snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
|
||||
log.G(ctx).WithField("key", key).Debug("usage")
|
||||
|
||||
return snapshots.Usage{}, errors.New("usage not implemented")
|
||||
var (
|
||||
id string
|
||||
err error
|
||||
info snapshots.Info
|
||||
usage snapshots.Usage
|
||||
)
|
||||
|
||||
err = s.withTransaction(ctx, false, func(ctx context.Context) error {
|
||||
id, info, usage, err = storage.GetInfo(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.Kind == snapshots.KindActive {
|
||||
deviceName := s.getDeviceName(id)
|
||||
usage.Size, err = s.pool.GetUsage(deviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if info.Parent != "" {
|
||||
// GetInfo returns total number of bytes used by a snapshot (including parent).
|
||||
// So subtract parent usage in order to get delta consumed by layer itself.
|
||||
_, _, parentUsage, err := storage.GetInfo(ctx, info.Parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
usage.Size -= parentUsage.Size
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return usage, err
|
||||
}
|
||||
|
||||
// Mounts return the list of mounts for the active or view snapshot
|
||||
@ -221,7 +256,22 @@ func (s *Snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
|
||||
log.G(ctx).WithFields(logrus.Fields{"name": name, "key": key}).Debug("commit")
|
||||
|
||||
return s.withTransaction(ctx, true, func(ctx context.Context) error {
|
||||
_, err := storage.CommitActive(ctx, key, name, snapshots.Usage{}, opts...)
|
||||
id, _, _, err := storage.GetInfo(ctx, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deviceName := s.getDeviceName(id)
|
||||
size, err := s.pool.GetUsage(deviceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
usage := snapshots.Usage{
|
||||
Size: size,
|
||||
}
|
||||
|
||||
_, err = storage.CommitActive(ctx, key, name, usage, opts...)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
@ -22,17 +22,23 @@ import (
|
||||
"context"
|
||||
_ "crypto/sha256"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/continuity/fs/fstest"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gotest.tools/assert"
|
||||
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/pkg/testutil"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
|
||||
"github.com/containerd/containerd/snapshots/devmapper/losetup"
|
||||
"github.com/containerd/containerd/snapshots/testsuite"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func TestSnapshotterSuite(t *testing.T) {
|
||||
@ -40,7 +46,7 @@ func TestSnapshotterSuite(t *testing.T) {
|
||||
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
|
||||
testsuite.SnapshotterSuite(t, "devmapper", func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
|
||||
snapshotterFn := func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
|
||||
// Create loopback devices for each test case
|
||||
_, loopDataDevice := createLoopbackDevice(t, root)
|
||||
_, loopMetaDevice := createLoopbackDevice(t, root)
|
||||
@ -73,5 +79,64 @@ func TestSnapshotterSuite(t *testing.T) {
|
||||
snap.cleanupFn = append([]closeFunc{removePool}, snap.cleanupFn...)
|
||||
|
||||
return snap, snap.Close, nil
|
||||
}
|
||||
|
||||
testsuite.SnapshotterSuite(t, "devmapper", snapshotterFn)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = namespaces.WithNamespace(ctx, "testsuite")
|
||||
|
||||
t.Run("DevMapperUsage", func(t *testing.T) {
|
||||
tempDir, err := ioutil.TempDir("", "snapshot-suite-usage")
|
||||
assert.NilError(t, err)
|
||||
defer os.RemoveAll(tempDir)
|
||||
|
||||
snapshotter, closer, err := snapshotterFn(ctx, tempDir)
|
||||
assert.NilError(t, err)
|
||||
defer closer()
|
||||
|
||||
testUsage(t, snapshotter)
|
||||
})
|
||||
}
|
||||
|
||||
// testUsage tests devmapper's Usage implementation. This is an approximate test as it's hard to
|
||||
// predict how many blocks will be consumed under different conditions and parameters.
|
||||
func testUsage(t *testing.T, snapshotter snapshots.Snapshotter) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create empty base layer
|
||||
_, err := snapshotter.Prepare(ctx, "prepare-1", "")
|
||||
assert.NilError(t, err)
|
||||
|
||||
emptyLayerUsage, err := snapshotter.Usage(ctx, "prepare-1")
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Should be > 0 as just written file system also consumes blocks
|
||||
assert.Assert(t, emptyLayerUsage.Size > 0)
|
||||
|
||||
err = snapshotter.Commit(ctx, "layer-1", "prepare-1")
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Create child layer with 1MB file
|
||||
|
||||
var (
|
||||
sizeBytes int64 = 1048576 // 1MB
|
||||
baseApplier = fstest.Apply(fstest.CreateRandomFile("/a", 12345679, sizeBytes, 0777))
|
||||
)
|
||||
|
||||
mounts, err := snapshotter.Prepare(ctx, "prepare-2", "layer-1")
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = mount.WithTempMount(ctx, mounts, baseApplier.Apply)
|
||||
assert.NilError(t, err)
|
||||
|
||||
err = snapshotter.Commit(ctx, "layer-2", "prepare-2")
|
||||
assert.NilError(t, err)
|
||||
|
||||
layer2Usage, err := snapshotter.Usage(ctx, "layer-2")
|
||||
assert.NilError(t, err)
|
||||
|
||||
// Should be at least 1 MB + fs metadata
|
||||
assert.Assert(t, layer2Usage.Size > sizeBytes)
|
||||
assert.Assert(t, layer2Usage.Size < sizeBytes+256*dmsetup.SectorSize)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user