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
19package devmapper
20
21import (
22	"context"
23	_ "crypto/sha256"
24	"fmt"
25	"io/ioutil"
26	"os"
27	"testing"
28	"time"
29
30	"github.com/containerd/continuity/fs/fstest"
31	"github.com/hashicorp/go-multierror"
32	"github.com/sirupsen/logrus"
33	"gotest.tools/v3/assert"
34
35	"github.com/containerd/containerd/mount"
36	"github.com/containerd/containerd/namespaces"
37	"github.com/containerd/containerd/pkg/testutil"
38	"github.com/containerd/containerd/snapshots"
39	"github.com/containerd/containerd/snapshots/devmapper/dmsetup"
40	"github.com/containerd/containerd/snapshots/devmapper/losetup"
41	"github.com/containerd/containerd/snapshots/testsuite"
42)
43
44func TestSnapshotterSuite(t *testing.T) {
45	testutil.RequiresRoot(t)
46
47	logrus.SetLevel(logrus.DebugLevel)
48
49	snapshotterFn := func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
50		// Create loopback devices for each test case
51		_, loopDataDevice := createLoopbackDevice(t, root)
52		_, loopMetaDevice := createLoopbackDevice(t, root)
53
54		poolName := fmt.Sprintf("containerd-snapshotter-suite-pool-%d", time.Now().Nanosecond())
55		err := dmsetup.CreatePool(poolName, loopDataDevice, loopMetaDevice, 64*1024/dmsetup.SectorSize)
56		assert.NilError(t, err, "failed to create pool %q", poolName)
57
58		config := &Config{
59			RootPath:      root,
60			PoolName:      poolName,
61			BaseImageSize: "16Mb",
62		}
63
64		snap, err := NewSnapshotter(context.Background(), config)
65		if err != nil {
66			return nil, nil, err
67		}
68
69		// Remove device mapper pool and detach loop devices after test completes
70		removePool := func() error {
71			result := multierror.Append(
72				snap.pool.RemovePool(ctx),
73				losetup.DetachLoopDevice(loopDataDevice, loopMetaDevice))
74
75			return result.ErrorOrNil()
76		}
77
78		// Pool cleanup should be called before closing metadata store (as we need to retrieve device names)
79		snap.cleanupFn = append([]closeFunc{removePool}, snap.cleanupFn...)
80
81		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)
99	})
100}
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.
104func 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}
143