Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Commit 127d02d

Browse files
committed
DeviceManager/LVM: Added unit tests
Used file backed loop device to mimic an logical volume group, so sudo/root user permission is required to run device manager unit tests.
1 parent beaa6d1 commit 127d02d

File tree

14 files changed

+714
-3
lines changed

14 files changed

+714
-3
lines changed

Gopkg.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
name = "github.com/kubernetes-csi/csi-test"
4545
version = "2.3.0"
4646

47+
[[constraint]]
48+
name = "gopkg.in/freddierice/go-losetup.v1"
49+
branch = "v1"
50+
4751
# We have to select the right version by name because of the "kubernetes-" prefix, dep doesn't
4852
# recognize these as normal releases. Upstream is considering to change the tagging,
4953
# see https://github.com/kubernetes/kubernetes/issues/72638.

pkg/pmem-device-manager/pmd-lvm.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func NewPmemDeviceManagerLVM() (PmemDeviceManager, error) {
6060
}
6161
ctx.Free()
6262

63+
return NewPmemDeviceManagerLVMForVGs(volumeGroups)
64+
}
65+
66+
func NewPmemDeviceManagerLVMForVGs(volumeGroups []string) (PmemDeviceManager, error) {
6367
devices, err := listDevices(volumeGroups...)
6468
if err != nil {
6569
return nil, err
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
/*
2+
Copyright 2019 Intel Corporation.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
package pmdmanager
7+
8+
import (
9+
"errors"
10+
"fmt"
11+
"io/ioutil"
12+
"math/rand"
13+
"os"
14+
"testing"
15+
16+
pmemexec "github.com/intel/pmem-csi/pkg/pmem-exec"
17+
. "github.com/onsi/ginkgo"
18+
. "github.com/onsi/gomega"
19+
20+
losetup "gopkg.in/freddierice/go-losetup.v1"
21+
)
22+
23+
const (
24+
vgname = "test-group"
25+
nsmode = "fsdax"
26+
vgsize = uint64(1) * 1024 * 1024 * 1024 // 1Gb
27+
28+
ModeLVM = "lvm"
29+
ModeDirect = "direct"
30+
)
31+
32+
var mode string = ModeLVM
33+
34+
func TestMain(m *testing.M) {
35+
RegisterFailHandler(Fail)
36+
37+
if deviceMode := os.Getenv("TEST_DEVICEMODE"); deviceMode == ModeDirect {
38+
mode = deviceMode
39+
}
40+
41+
os.Exit(m.Run())
42+
}
43+
44+
func TestPmdLVM(t *testing.T) {
45+
RunSpecs(t, fmt.Sprintf("PMEM Device manager Suite(Mode: %s)", mode))
46+
}
47+
48+
var _ = Describe("DeviceManager", func() {
49+
var dm PmemDeviceManager
50+
var vg *testVGS
51+
var cleanupList map[string]bool = map[string]bool{}
52+
var err error
53+
54+
BeforeEach(func() {
55+
precheck()
56+
57+
if mode == ModeLVM {
58+
vg, err = createTestVGS(vgname, nsmode, vgsize)
59+
Expect(err).Should(BeNil(), "Failed to create volume group")
60+
61+
dm, err = NewPmemDeviceManagerLVMForVGs([]string{vg.name})
62+
} else {
63+
dm, err = NewPmemDeviceManagerNdctl()
64+
}
65+
Expect(err).Should(BeNil(), "Failed to create LVM device manager")
66+
67+
})
68+
69+
AfterEach(func() {
70+
for devName, ok := range cleanupList {
71+
if !ok {
72+
continue
73+
}
74+
By("Cleaning up device: " + devName)
75+
dm.DeleteDevice(devName, false)
76+
}
77+
if mode == ModeLVM {
78+
err := vg.Clean()
79+
Expect(err).Should(BeNil(), "Failed to create LVM device manager")
80+
}
81+
})
82+
83+
//Context("LVM", func() {
84+
It("Should create a new device", func() {
85+
name := "test-dev"
86+
size := uint64(2) * 1024 * 1024 // 2Mb
87+
err := dm.CreateDevice(name, size, nsmode)
88+
Expect(err).Should(BeNil(), "Failed to create new device")
89+
90+
cleanupList[name] = true
91+
92+
dev, err := dm.GetDevice(name)
93+
Expect(err).Should(BeNil(), "Failed to retrieve device info")
94+
Expect(dev.VolumeId).Should(Equal(name), "Name mismatch")
95+
Expect(dev.Size >= size).Should(BeTrue(), "Size mismatch")
96+
Expect(dev.Path).ShouldNot(BeNil(), "Null device path")
97+
})
98+
99+
It("Should fail to retrieve non-existent device", func() {
100+
dev, err := dm.GetDevice("unknown")
101+
Expect(err).ShouldNot(BeNil(), "Error expected")
102+
Expect(errors.Is(err, ErrDeviceNotFound)).Should(BeTrue(), "expected error is device not found error")
103+
Expect(dev).Should(BeNil(), "returned device should be nil")
104+
})
105+
106+
It("Should list devices", func() {
107+
max_devices := 4
108+
max_deletes := 2
109+
sizes := map[string]uint64{}
110+
111+
list, err := dm.ListDevices()
112+
Expect(err).Should(BeNil(), "Failed to list devices")
113+
Expect(len(list)).Should(BeEquivalentTo(0), "count mismatch")
114+
115+
for i := 1; i <= max_devices; i++ {
116+
name := fmt.Sprintf("list-dev-%d", i)
117+
sizes[name] = rand.Uint64() % 16 * 1024 * 1024
118+
err := dm.CreateDevice(name, sizes[name], nsmode)
119+
Expect(err).Should(BeNil(), "Failed to create new device")
120+
cleanupList[name] = true
121+
}
122+
list, err = dm.ListDevices()
123+
Expect(err).Should(BeNil(), "Failed to list devices")
124+
Expect(len(list)).Should(BeEquivalentTo(max_devices), "count mismatch")
125+
for _, dev := range list {
126+
size, ok := sizes[dev.VolumeId]
127+
Expect(ok).Should(BeTrue(), "Unexpected device name:"+dev.VolumeId)
128+
Expect(dev.Size >= size).Should(BeTrue(), "Device size mismatch")
129+
}
130+
131+
for i := 1; i <= max_deletes; i++ {
132+
name := fmt.Sprintf("list-dev-%d", i)
133+
delete(sizes, name)
134+
err = dm.DeleteDevice(name, false)
135+
Expect(err).Should(BeNil(), "Error while deleting device '"+name+"'")
136+
cleanupList[name] = false
137+
}
138+
139+
// List device after deleting a device
140+
list, err = dm.ListDevices()
141+
Expect(err).Should(BeNil(), "Failed to list devices")
142+
Expect(len(list)).Should(BeEquivalentTo(max_devices-max_deletes), "count mismatch")
143+
for _, dev := range list {
144+
size, ok := sizes[dev.VolumeId]
145+
Expect(ok).Should(BeTrue(), "Unexpected device name:"+dev.VolumeId)
146+
Expect(dev.Size >= size).Should(BeTrue(), "Device size mismatch")
147+
}
148+
})
149+
150+
It("Should delete devices", func() {
151+
name := "delete-dev"
152+
size := uint64(2) * 1024 * 1024 // 2Mb
153+
err := dm.CreateDevice(name, size, nsmode)
154+
Expect(err).Should(BeNil(), "Failed to create new device")
155+
cleanupList[name] = true
156+
157+
dev, err := dm.GetDevice(name)
158+
Expect(err).Should(BeNil(), "Failed to retrieve device info")
159+
Expect(dev.VolumeId).Should(Equal(name), "Name mismatch")
160+
Expect(dev.Size >= size).Should(BeTrue(), "Size mismatch")
161+
Expect(dev.Path).ShouldNot(BeNil(), "Null device path")
162+
163+
mountPath, err := mountDevice(dev)
164+
Expect(err).Should(BeNil(), "Failed to create mount path: %s", mountPath)
165+
166+
defer unmount(mountPath)
167+
168+
// Delete should fail as the device is in use
169+
err = dm.DeleteDevice(name, true)
170+
Expect(err).ShouldNot(BeNil(), "Error expected when deleting device in use: %s", dev.VolumeId)
171+
Expect(errors.Is(err, ErrDeviceInUse)).Should(BeTrue(), "Expected device busy error: %s", dev.VolumeId)
172+
cleanupList[name] = false
173+
174+
err = unmount(mountPath)
175+
Expect(err).Should(BeNil(), "Failed to unmount the device: %s", dev.VolumeId)
176+
177+
// Delete should succeed
178+
err = dm.DeleteDevice(name, true)
179+
Expect(err).Should(BeNil(), "Failed to delete device")
180+
181+
dev, err = dm.GetDevice(name)
182+
Expect(err).ShouldNot(BeNil(), "GetDevice() should fail on deleted device")
183+
Expect(errors.Is(err, ErrDeviceNotFound)).Should(BeTrue(), "expected error is os.ErrNotExist")
184+
Expect(dev).Should(BeNil(), "returned device should be nil")
185+
186+
// Delete call should not return any error on non-existing device
187+
err = dm.DeleteDevice(name, true)
188+
Expect(err).Should(BeNil(), "DeleteDevice() is not idempotent")
189+
})
190+
// })
191+
})
192+
193+
func precheck() {
194+
if os.Geteuid() != 0 {
195+
Skip("Required root used privilege to these tests", 1)
196+
}
197+
198+
info, err := os.Stat(losetup.LoopControlPath)
199+
if err != nil {
200+
Skip(fmt.Sprintf("Stat(%s) failure: %s", losetup.LoopControlPath, err.Error()), 1)
201+
}
202+
if isDev := info.Mode()&os.ModeDevice != 0; !isDev {
203+
Skip(fmt.Sprintf("%s is not a loop device file", losetup.LoopControlPath), 1)
204+
}
205+
}
206+
207+
type testVGS struct {
208+
name string
209+
loopDev losetup.Device
210+
backedFile string
211+
}
212+
213+
func createTestVGS(vgname, nsmode string, size uint64) (*testVGS, error) {
214+
var err error
215+
var file *os.File
216+
var dev losetup.Device
217+
var out string
218+
219+
By("Creating temporary file")
220+
if file, err = ioutil.TempFile("", "test-lvm-dev"); err != nil {
221+
By("Cleaning temporary file")
222+
return nil, fmt.Errorf("Fail to create temporary file : %s", err.Error())
223+
}
224+
225+
defer func() {
226+
if err != nil && file != nil {
227+
os.Remove(file.Name())
228+
}
229+
}()
230+
231+
By("Closing file")
232+
if err = file.Close(); err != nil {
233+
return nil, fmt.Errorf("Fail to close file: %s", err.Error())
234+
}
235+
236+
By("File truncating")
237+
if err = os.Truncate(file.Name(), int64(size)); err != nil {
238+
return nil, fmt.Errorf("Fail to truncate file: %s", err.Error())
239+
}
240+
241+
By("losetup.Attach")
242+
dev, err = losetup.Attach(file.Name(), 0, false)
243+
if err != nil {
244+
return nil, fmt.Errorf("losetup failure: %s", err.Error())
245+
}
246+
247+
defer func() {
248+
if err != nil {
249+
By("losetup.Detach")
250+
dev.Detach() // nolint errcheck
251+
}
252+
}()
253+
254+
if err = waitDeviceAppears(&PmemDeviceInfo{Path: dev.Path()}); err != nil {
255+
return nil, fmt.Errorf("created loop device not appeared: %s", err.Error())
256+
}
257+
258+
By("Creating volume group")
259+
// TODO: reuse vgm code
260+
cmdArgs := []string{"--force", dev.Path()}
261+
if out, err = pmemexec.RunCommand("pvcreate", cmdArgs...); err != nil { // nolint gosec
262+
return nil, fmt.Errorf("pvcreate failure(output:%s): %s", out, err.Error())
263+
}
264+
265+
cmdArgs = []string{"--force", vgname, dev.Path()}
266+
if out, err = pmemexec.RunCommand("vgcreate", cmdArgs...); err != nil { // nolint gosec
267+
return nil, fmt.Errorf("vgcreate failure(output:%s): %s", out, err.Error())
268+
}
269+
270+
defer func() {
271+
if err != nil {
272+
By("Removing volume group")
273+
pmemexec.RunCommand("vgremove", "--force", vgname)
274+
}
275+
}()
276+
277+
By("Append tag(s) to volume group")
278+
if out, err = pmemexec.RunCommand("vgchange", "--addtag", string(nsmode), vgname); err != nil {
279+
return nil, fmt.Errorf("vgchange failure(output:%s): %s", out, err.Error())
280+
}
281+
282+
return &testVGS{
283+
name: vgname,
284+
loopDev: dev,
285+
backedFile: file.Name(),
286+
}, nil
287+
}
288+
289+
func (vg *testVGS) Clean() error {
290+
By("Removing volume group")
291+
if out, err := pmemexec.RunCommand("vgremove", "--force", vg.name); err != nil {
292+
return fmt.Errorf("Fail to remove volume group(output:%s): %s", out, err.Error())
293+
}
294+
295+
By("losetup.Detach()")
296+
if err := vg.loopDev.Detach(); err != nil {
297+
return fmt.Errorf("Fail detatch loop device: %s", err.Error())
298+
}
299+
300+
By("Removing temp file")
301+
if err := os.Remove(vg.backedFile); err != nil {
302+
return fmt.Errorf("Fail remove temporary file: %s", err.Error())
303+
}
304+
305+
return nil
306+
}
307+
308+
func mountDevice(device *PmemDeviceInfo) (string, error) {
309+
targetPath, err := ioutil.TempDir("/tmp", "lmv-mnt-path-")
310+
if err != nil {
311+
return "", err
312+
}
313+
314+
cmd := "mkfs.ext4"
315+
args := []string{"-b 4096", "-F", device.Path}
316+
317+
if _, err := pmemexec.RunCommand(cmd, args...); err != nil {
318+
os.Remove(targetPath)
319+
return "", err
320+
}
321+
322+
cmd = "mount"
323+
args = []string{"-c", device.Path, targetPath}
324+
325+
if _, err := pmemexec.RunCommand(cmd, args...); err != nil {
326+
os.Remove(targetPath)
327+
return "", err
328+
}
329+
330+
return targetPath, nil
331+
}
332+
333+
func unmount(path string) error {
334+
args := []string{path}
335+
if _, err := pmemexec.RunCommand("umount", args...); err != nil {
336+
return err
337+
}
338+
return os.Remove(path)
339+
}

0 commit comments

Comments
 (0)