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	"io/ioutil"
24	"os"
25	"path/filepath"
26	"strconv"
27	"testing"
28
29	"github.com/pkg/errors"
30	"go.etcd.io/bbolt"
31	"gotest.tools/assert"
32	is "gotest.tools/assert/cmp"
33)
34
35var (
36	testCtx = context.Background()
37)
38
39func TestPoolMetadata_AddDevice(t *testing.T) {
40	tempDir, store := createStore(t)
41	defer cleanupStore(t, tempDir, store)
42
43	expected := &DeviceInfo{
44		Name:       "test2",
45		ParentName: "test1",
46		Size:       1,
47		State:      Activated,
48	}
49
50	err := store.AddDevice(testCtx, expected)
51	assert.NilError(t, err)
52
53	result, err := store.GetDevice(testCtx, "test2")
54	assert.NilError(t, err)
55
56	assert.Equal(t, expected.Name, result.Name)
57	assert.Equal(t, expected.ParentName, result.ParentName)
58	assert.Equal(t, expected.Size, result.Size)
59	assert.Equal(t, expected.State, result.State)
60	assert.Assert(t, result.DeviceID != 0)
61	assert.Equal(t, expected.DeviceID, result.DeviceID)
62}
63
64func TestPoolMetadata_AddDeviceRollback(t *testing.T) {
65	tempDir, store := createStore(t)
66	defer cleanupStore(t, tempDir, store)
67
68	err := store.AddDevice(testCtx, &DeviceInfo{Name: ""})
69	assert.Assert(t, err != nil)
70
71	_, err = store.GetDevice(testCtx, "")
72	assert.Equal(t, ErrNotFound, err)
73}
74
75func TestPoolMetadata_AddDeviceDuplicate(t *testing.T) {
76	tempDir, store := createStore(t)
77	defer cleanupStore(t, tempDir, store)
78
79	err := store.AddDevice(testCtx, &DeviceInfo{Name: "test"})
80	assert.NilError(t, err)
81
82	err = store.AddDevice(testCtx, &DeviceInfo{Name: "test"})
83	assert.Equal(t, ErrAlreadyExists, errors.Cause(err))
84}
85
86func TestPoolMetadata_ReuseDeviceID(t *testing.T) {
87	tempDir, store := createStore(t)
88	defer cleanupStore(t, tempDir, store)
89
90	info1 := &DeviceInfo{Name: "test1"}
91	err := store.AddDevice(testCtx, info1)
92	assert.NilError(t, err)
93
94	info2 := &DeviceInfo{Name: "test2"}
95	err = store.AddDevice(testCtx, info2)
96	assert.NilError(t, err)
97
98	assert.Assert(t, info1.DeviceID != info2.DeviceID)
99	assert.Assert(t, info1.DeviceID != 0)
100
101	err = store.RemoveDevice(testCtx, info2.Name)
102	assert.NilError(t, err)
103
104	info3 := &DeviceInfo{Name: "test3"}
105	err = store.AddDevice(testCtx, info3)
106	assert.NilError(t, err)
107
108	assert.Equal(t, info2.DeviceID, info3.DeviceID)
109}
110
111func TestPoolMetadata_RemoveDevice(t *testing.T) {
112	tempDir, store := createStore(t)
113	defer cleanupStore(t, tempDir, store)
114
115	err := store.AddDevice(testCtx, &DeviceInfo{Name: "test"})
116	assert.NilError(t, err)
117
118	err = store.RemoveDevice(testCtx, "test")
119	assert.NilError(t, err)
120
121	_, err = store.GetDevice(testCtx, "test")
122	assert.Equal(t, ErrNotFound, err)
123}
124
125func TestPoolMetadata_UpdateDevice(t *testing.T) {
126	tempDir, store := createStore(t)
127	defer cleanupStore(t, tempDir, store)
128
129	oldInfo := &DeviceInfo{
130		Name:       "test1",
131		ParentName: "test2",
132		Size:       3,
133		State:      Activated,
134	}
135
136	err := store.AddDevice(testCtx, oldInfo)
137	assert.NilError(t, err)
138
139	err = store.UpdateDevice(testCtx, oldInfo.Name, func(info *DeviceInfo) error {
140		info.ParentName = "test5"
141		info.Size = 6
142		info.State = Created
143		return nil
144	})
145
146	assert.NilError(t, err)
147
148	newInfo, err := store.GetDevice(testCtx, "test1")
149	assert.NilError(t, err)
150
151	assert.Equal(t, "test1", newInfo.Name)
152	assert.Equal(t, "test5", newInfo.ParentName)
153	assert.Assert(t, newInfo.Size == 6)
154	assert.Equal(t, Created, newInfo.State)
155}
156
157func TestPoolMetadata_MarkFaulty(t *testing.T) {
158	tempDir, store := createStore(t)
159	defer cleanupStore(t, tempDir, store)
160
161	info := &DeviceInfo{Name: "test"}
162	err := store.AddDevice(testCtx, info)
163	assert.NilError(t, err)
164
165	err = store.MarkFaulty(testCtx, "test")
166	assert.NilError(t, err)
167
168	saved, err := store.GetDevice(testCtx, info.Name)
169	assert.NilError(t, err)
170	assert.Equal(t, saved.State, Faulty)
171	assert.Assert(t, saved.DeviceID > 0)
172
173	// Make sure a device ID marked as faulty as well
174	err = store.db.View(func(tx *bbolt.Tx) error {
175		bucket := tx.Bucket(deviceIDBucketName)
176		key := strconv.FormatUint(uint64(saved.DeviceID), 10)
177		value := bucket.Get([]byte(key))
178		assert.Equal(t, value[0], byte(deviceFaulty))
179		return nil
180	})
181	assert.NilError(t, err)
182}
183
184func TestPoolMetadata_WalkDevices(t *testing.T) {
185	tempDir, store := createStore(t)
186	defer cleanupStore(t, tempDir, store)
187
188	err := store.AddDevice(testCtx, &DeviceInfo{Name: "device1", DeviceID: 1, State: Created})
189	assert.NilError(t, err)
190
191	err = store.AddDevice(testCtx, &DeviceInfo{Name: "device2", DeviceID: 2, State: Faulty})
192	assert.NilError(t, err)
193
194	called := 0
195	err = store.WalkDevices(testCtx, func(info *DeviceInfo) error {
196		called++
197		switch called {
198		case 1:
199			assert.Equal(t, "device1", info.Name)
200			assert.Equal(t, uint32(1), info.DeviceID)
201			assert.Equal(t, Created, info.State)
202		case 2:
203			assert.Equal(t, "device2", info.Name)
204			assert.Equal(t, uint32(2), info.DeviceID)
205			assert.Equal(t, Faulty, info.State)
206		default:
207			t.Error("unexpected walk call")
208		}
209
210		return nil
211	})
212	assert.NilError(t, err)
213	assert.Equal(t, called, 2)
214}
215
216func TestPoolMetadata_GetDeviceNames(t *testing.T) {
217	tempDir, store := createStore(t)
218	defer cleanupStore(t, tempDir, store)
219
220	err := store.AddDevice(testCtx, &DeviceInfo{Name: "test1"})
221	assert.NilError(t, err)
222
223	err = store.AddDevice(testCtx, &DeviceInfo{Name: "test2"})
224	assert.NilError(t, err)
225
226	names, err := store.GetDeviceNames(testCtx)
227	assert.NilError(t, err)
228	assert.Assert(t, is.Len(names, 2))
229
230	assert.Equal(t, "test1", names[0])
231	assert.Equal(t, "test2", names[1])
232}
233
234func createStore(t *testing.T) (tempDir string, store *PoolMetadata) {
235	tempDir, err := ioutil.TempDir("", "pool-metadata-")
236	assert.NilError(t, err, "couldn't create temp directory for metadata tests")
237
238	path := filepath.Join(tempDir, "test.db")
239	metadata, err := NewPoolMetadata(path)
240	assert.NilError(t, err)
241
242	return tempDir, metadata
243}
244
245func cleanupStore(t *testing.T, tempDir string, store *PoolMetadata) {
246	err := store.Close()
247	assert.NilError(t, err, "failed to close metadata store")
248
249	err = os.RemoveAll(tempDir)
250	assert.NilError(t, err, "failed to cleanup temp directory")
251}
252