Skip to content

Commit 87289a0

Browse files
committed
devmapper: implement Usage
Signed-off-by: Maksym Pavlenko <[email protected]>
1 parent 010b4da commit 87289a0

4 files changed

Lines changed: 157 additions & 9 deletions

File tree

snapshots/devmapper/pool_device.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ package devmapper
2121
import (
2222
"context"
2323
"path/filepath"
24+
"strconv"
2425

25-
"github.com/containerd/containerd/log"
26-
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
2726
"github.com/hashicorp/go-multierror"
2827
"github.com/pkg/errors"
28+
29+
"github.com/containerd/containerd/log"
30+
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
2931
)
3032

3133
// 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 {
298300
return true
299301
}
300302

303+
// GetUsage reports total size in bytes consumed by a thin-device.
304+
// It relies on the number of used blocks reported by 'dmsetup status'.
305+
// The output looks like:
306+
// device2: 0 204800 thin 17280 204799
307+
// Where 17280 is the number of used sectors
308+
func (p *PoolDevice) GetUsage(deviceName string) (int64, error) {
309+
status, err := dmsetup.Status(deviceName)
310+
if err != nil {
311+
return 0, errors.Wrapf(err, "can't get status for device %q", deviceName)
312+
}
313+
314+
if len(status.Params) == 0 {
315+
return 0, errors.Errorf("failed to get the number of used blocks, unexpected output from dmsetup status")
316+
}
317+
318+
count, err := strconv.ParseInt(status.Params[0], 10, 64)
319+
if err != nil {
320+
return 0, errors.Wrapf(err, "failed to parse status params: %q", status.Params[0])
321+
}
322+
323+
return count * dmsetup.SectorSize, nil
324+
}
325+
301326
// RemoveDevice completely wipes out thin device from thin-pool and frees it's device ID
302327
func (p *PoolDevice) RemoveDevice(ctx context.Context, deviceName string) error {
303328
info, err := p.metadata.GetDevice(ctx, deviceName)

snapshots/devmapper/pool_device_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ func testCreateThinDevice(t *testing.T, pool *PoolDevice) {
168168
assert.NilError(t, err)
169169

170170
assert.Assert(t, deviceInfo1.DeviceID != deviceInfo2.DeviceID, "assigned device ids should be different")
171+
172+
usage, err := pool.GetUsage(thinDevice1)
173+
assert.NilError(t, err)
174+
assert.Equal(t, usage, int64(0))
171175
}
172176

173177
func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
@@ -180,6 +184,10 @@ func testMakeFileSystem(t *testing.T, pool *PoolDevice) {
180184

181185
output, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
182186
assert.NilError(t, err, "failed to make filesystem on '%s': %s", thinDevice1, string(output))
187+
188+
usage, err := pool.GetUsage(thinDevice1)
189+
assert.NilError(t, err)
190+
assert.Assert(t, usage > 0)
183191
}
184192

185193
func testCreateSnapshot(t *testing.T, pool *PoolDevice) {

snapshots/devmapper/snapshotter.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,46 @@ func (s *Snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpath
156156
return info, err
157157
}
158158

159-
// Usage not yet implemented
159+
// Usage returns the resource usage of an active or committed snapshot excluding the usage of parent snapshots.
160160
func (s *Snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) {
161161
log.G(ctx).WithField("key", key).Debug("usage")
162162

163-
return snapshots.Usage{}, errors.New("usage not implemented")
163+
var (
164+
id string
165+
err error
166+
info snapshots.Info
167+
usage snapshots.Usage
168+
)
169+
170+
err = s.withTransaction(ctx, false, func(ctx context.Context) error {
171+
id, info, usage, err = storage.GetInfo(ctx, key)
172+
if err != nil {
173+
return err
174+
}
175+
176+
if info.Kind == snapshots.KindActive {
177+
deviceName := s.getDeviceName(id)
178+
usage.Size, err = s.pool.GetUsage(deviceName)
179+
if err != nil {
180+
return err
181+
}
182+
}
183+
184+
if info.Parent != "" {
185+
// GetInfo returns total number of bytes used by a snapshot (including parent).
186+
// So subtract parent usage in order to get delta consumed by layer itself.
187+
_, _, parentUsage, err := storage.GetInfo(ctx, info.Parent)
188+
if err != nil {
189+
return err
190+
}
191+
192+
usage.Size -= parentUsage.Size
193+
}
194+
195+
return err
196+
})
197+
198+
return usage, err
164199
}
165200

166201
// 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
221256
log.G(ctx).WithFields(logrus.Fields{"name": name, "key": key}).Debug("commit")
222257

223258
return s.withTransaction(ctx, true, func(ctx context.Context) error {
224-
_, err := storage.CommitActive(ctx, key, name, snapshots.Usage{}, opts...)
259+
id, _, _, err := storage.GetInfo(ctx, key)
260+
if err != nil {
261+
return err
262+
}
263+
264+
deviceName := s.getDeviceName(id)
265+
size, err := s.pool.GetUsage(deviceName)
266+
if err != nil {
267+
return err
268+
}
269+
270+
usage := snapshots.Usage{
271+
Size: size,
272+
}
273+
274+
_, err = storage.CommitActive(ctx, key, name, usage, opts...)
225275
return err
226276
})
227277
}

snapshots/devmapper/snapshotter_test.go

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,31 @@ import (
2222
"context"
2323
_ "crypto/sha256"
2424
"fmt"
25+
"io/ioutil"
26+
"os"
2527
"testing"
2628
"time"
2729

30+
"github.com/containerd/continuity/fs/fstest"
31+
"github.com/hashicorp/go-multierror"
32+
"github.com/sirupsen/logrus"
33+
"gotest.tools/assert"
34+
35+
"github.com/containerd/containerd/mount"
36+
"github.com/containerd/containerd/namespaces"
2837
"github.com/containerd/containerd/pkg/testutil"
2938
"github.com/containerd/containerd/snapshots"
3039
"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
3140
"github.com/containerd/containerd/snapshots/devmapper/losetup"
3241
"github.com/containerd/containerd/snapshots/testsuite"
33-
"github.com/hashicorp/go-multierror"
34-
"github.com/sirupsen/logrus"
35-
"gotest.tools/assert"
3642
)
3743

3844
func TestSnapshotterSuite(t *testing.T) {
3945
testutil.RequiresRoot(t)
4046

4147
logrus.SetLevel(logrus.DebugLevel)
4248

43-
testsuite.SnapshotterSuite(t, "devmapper", func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
49+
snapshotterFn := func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
4450
// Create loopback devices for each test case
4551
_, loopDataDevice := createLoopbackDevice(t, root)
4652
_, loopMetaDevice := createLoopbackDevice(t, root)
@@ -73,5 +79,64 @@ func TestSnapshotterSuite(t *testing.T) {
7379
snap.cleanupFn = append([]closeFunc{removePool}, snap.cleanupFn...)
7480

7581
return snap, snap.Close, nil
82+
}
83+
84+
testsuite.SnapshotterSuite(t, "devmapper", snapshotterFn)
85+
86+
ctx := context.Background()
87+
ctx = namespaces.WithNamespace(ctx, "testsuite")
88+
89+
t.Run("DevMapperUsage", func(t *testing.T) {
90+
tempDir, err := ioutil.TempDir("", "snapshot-suite-usage")
91+
assert.NilError(t, err)
92+
defer os.RemoveAll(tempDir)
93+
94+
snapshotter, closer, err := snapshotterFn(ctx, tempDir)
95+
assert.NilError(t, err)
96+
defer closer()
97+
98+
testUsage(t, snapshotter)
7699
})
77100
}
101+
102+
// testUsage tests devmapper's Usage implementation. This is an approximate test as it's hard to
103+
// predict how many blocks will be consumed under different conditions and parameters.
104+
func testUsage(t *testing.T, snapshotter snapshots.Snapshotter) {
105+
ctx := context.Background()
106+
107+
// Create empty base layer
108+
_, err := snapshotter.Prepare(ctx, "prepare-1", "")
109+
assert.NilError(t, err)
110+
111+
emptyLayerUsage, err := snapshotter.Usage(ctx, "prepare-1")
112+
assert.NilError(t, err)
113+
114+
// Should be > 0 as just written file system also consumes blocks
115+
assert.Assert(t, emptyLayerUsage.Size > 0)
116+
117+
err = snapshotter.Commit(ctx, "layer-1", "prepare-1")
118+
assert.NilError(t, err)
119+
120+
// Create child layer with 1MB file
121+
122+
var (
123+
sizeBytes int64 = 1048576 // 1MB
124+
baseApplier = fstest.Apply(fstest.CreateRandomFile("/a", 12345679, sizeBytes, 0777))
125+
)
126+
127+
mounts, err := snapshotter.Prepare(ctx, "prepare-2", "layer-1")
128+
assert.NilError(t, err)
129+
130+
err = mount.WithTempMount(ctx, mounts, baseApplier.Apply)
131+
assert.NilError(t, err)
132+
133+
err = snapshotter.Commit(ctx, "layer-2", "prepare-2")
134+
assert.NilError(t, err)
135+
136+
layer2Usage, err := snapshotter.Usage(ctx, "layer-2")
137+
assert.NilError(t, err)
138+
139+
// Should be at least 1 MB + fs metadata
140+
assert.Assert(t, layer2Usage.Size > sizeBytes)
141+
assert.Assert(t, layer2Usage.Size < sizeBytes+256*dmsetup.SectorSize)
142+
}

0 commit comments

Comments
 (0)