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

Commit ff07726

Browse files
committed
DeviceManager: Added unit tests
Tests for both device managers: direct and LVM. LVM tests used file backed loop device to mimic an logical volume group, This requires /dev/loop-control file on host where the tests are run. Intentionally disable running these device manager unit tests as it requires sudo/root user permission.
1 parent 8b22191 commit ff07726

File tree

15 files changed

+695
-1
lines changed

15 files changed

+695
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
google.golang.org/appengine v1.6.5 // indirect
3939
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03 // indirect
4040
google.golang.org/grpc v1.24.0
41+
gopkg.in/freddierice/go-losetup.v1 v1.0.0-20170407175016-fc9adea44124
4142
gopkg.in/inf.v0 v0.9.1 // indirect
4243
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
4344
k8s.io/api v0.0.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
622622
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
623623
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
624624
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
625+
gopkg.in/freddierice/go-losetup.v1 v1.0.0-20170407175016-fc9adea44124 h1:aPcd9iBdqpFyYkoGRQbQd+asp162GIRDvAVB0FhLxhc=
626+
gopkg.in/freddierice/go-losetup.v1 v1.0.0-20170407175016-fc9adea44124/go.mod h1:6LXpUYtVsrx91XiupFRJ8jVKOqLZf5PrbEVSGHta/84=
625627
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
626628
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
627629
gopkg.in/gcfg.v1 v1.2.0 h1:0HIbH907iBTAntm+88IJV2qmJALDAh8sPekI9Vc1fm0=

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: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
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+
func TestMain(m *testing.M) {
33+
RegisterFailHandler(Fail)
34+
35+
os.Exit(m.Run())
36+
}
37+
38+
func TestPmd(t *testing.T) {
39+
RunSpecs(t, "PMEM Device manager Suite")
40+
}
41+
42+
var _ = Describe("DeviceManager", func() {
43+
Context(ModeLVM, func() { runTests(ModeLVM) })
44+
Context(ModeDirect, func() { runTests(ModeDirect) })
45+
})
46+
47+
func runTests(mode string) {
48+
var dm PmemDeviceManager
49+
var vg *testVGS
50+
var cleanupList map[string]bool
51+
var err error
52+
53+
BeforeEach(func() {
54+
precheck()
55+
56+
cleanupList = map[string]bool{}
57+
58+
if mode == ModeLVM {
59+
vg, err = createTestVGS(vgname, nsmode, vgsize)
60+
Expect(err).Should(BeNil(), "Failed to create volume group")
61+
62+
dm, err = NewPmemDeviceManagerLVMForVGs([]string{vg.name})
63+
} else {
64+
dm, err = NewPmemDeviceManagerNdctl()
65+
}
66+
Expect(err).Should(BeNil(), "Failed to create LVM device manager")
67+
68+
})
69+
70+
AfterEach(func() {
71+
for devName, ok := range cleanupList {
72+
if !ok {
73+
continue
74+
}
75+
By("Cleaning up device: " + devName)
76+
dm.DeleteDevice(devName, false)
77+
}
78+
if mode == ModeLVM {
79+
err := vg.Clean()
80+
Expect(err).Should(BeNil(), "Failed to create LVM device manager")
81+
}
82+
})
83+
84+
It("Should create a new device", func() {
85+
name := "test-dev-new"
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+
func precheck() {
193+
if os.Geteuid() != 0 {
194+
Skip("Root privileges are required to run these tests", 1)
195+
}
196+
197+
info, err := os.Stat(losetup.LoopControlPath)
198+
if err != nil {
199+
Skip(fmt.Sprintf("Stat(%s) failure: %s", losetup.LoopControlPath, err.Error()), 1)
200+
}
201+
if isDev := info.Mode()&os.ModeDevice != 0; !isDev {
202+
Skip(fmt.Sprintf("%s is not a loop device file", losetup.LoopControlPath), 1)
203+
}
204+
}
205+
206+
type testVGS struct {
207+
name string
208+
loopDev losetup.Device
209+
backedFile string
210+
}
211+
212+
func createTestVGS(vgname, nsmode string, size uint64) (*testVGS, error) {
213+
var err error
214+
var file *os.File
215+
var dev losetup.Device
216+
var out string
217+
218+
By("Creating temporary file")
219+
if file, err = ioutil.TempFile("", "test-lvm-dev"); err != nil {
220+
By("Cleaning temporary file")
221+
return nil, fmt.Errorf("Fail to create temporary file : %s", err.Error())
222+
}
223+
224+
defer func() {
225+
if err != nil && file != nil {
226+
By("Removing tmp file due to failure")
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 due to failure")
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 physical volume")
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+
By("Creating volume group")
266+
cmdArgs = []string{"--force", vgname, dev.Path()}
267+
if out, err = pmemexec.RunCommand("vgcreate", cmdArgs...); err != nil { // nolint gosec
268+
return nil, fmt.Errorf("vgcreate failure(output:%s): %s", out, err.Error())
269+
}
270+
271+
defer func() {
272+
if err != nil {
273+
By("Removing volume group due to failure")
274+
pmemexec.RunCommand("vgremove", "--force", vgname)
275+
}
276+
}()
277+
278+
By("Append tag(s) to volume group")
279+
if out, err = pmemexec.RunCommand("vgchange", "--addtag", string(nsmode), vgname); err != nil {
280+
return nil, fmt.Errorf("vgchange failure(output:%s): %s", out, err.Error())
281+
}
282+
283+
return &testVGS{
284+
name: vgname,
285+
loopDev: dev,
286+
backedFile: file.Name(),
287+
}, nil
288+
}
289+
290+
func (vg *testVGS) Clean() error {
291+
By("Removing volume group")
292+
if out, err := pmemexec.RunCommand("vgremove", "--force", vg.name); err != nil {
293+
return fmt.Errorf("Fail to remove volume group(output:%s): %s", out, err.Error())
294+
}
295+
296+
By("losetup.Detach()")
297+
if err := vg.loopDev.Detach(); err != nil {
298+
return fmt.Errorf("Fail detatch loop device: %s", err.Error())
299+
}
300+
301+
By("Removing temp file")
302+
if err := os.Remove(vg.backedFile); err != nil {
303+
return fmt.Errorf("Fail remove temporary file: %s", err.Error())
304+
}
305+
306+
return nil
307+
}
308+
309+
func mountDevice(device *PmemDeviceInfo) (string, error) {
310+
targetPath, err := ioutil.TempDir("/tmp", "lmv-mnt-path-")
311+
if err != nil {
312+
return "", err
313+
}
314+
315+
cmd := "mkfs.ext4"
316+
args := []string{"-b 4096", "-F", device.Path}
317+
318+
if _, err := pmemexec.RunCommand(cmd, args...); err != nil {
319+
os.Remove(targetPath)
320+
return "", err
321+
}
322+
323+
cmd = "mount"
324+
args = []string{"-c", device.Path, targetPath}
325+
326+
if _, err := pmemexec.RunCommand(cmd, args...); err != nil {
327+
os.Remove(targetPath)
328+
return "", err
329+
}
330+
331+
return targetPath, nil
332+
}
333+
334+
func unmount(path string) error {
335+
args := []string{path}
336+
if _, err := pmemexec.RunCommand("umount", args...); err != nil {
337+
return err
338+
}
339+
return os.Remove(path)
340+
}

test/test.make

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ test_e2e: start
145145
.PHONY: run_tests
146146
test: run_tests
147147
RUN_TESTS = TEST_WORK=$(abspath _work) \
148-
$(TEST_CMD) $(shell $(GO) list $(TEST_ARGS) | sed -e 's;$(IMPORT_PATH);.;')
148+
$(TEST_CMD) $(shell $(GO) list $(TEST_ARGS) | grep -v pmem-device-manager | sed -e 's;$(IMPORT_PATH);.;')
149149
run_tests: _work/pmem-ca/.ca-stamp _work/evil-ca/.ca-stamp check-go-version-$(GO_BINARY)
150150
$(RUN_TESTS)
151151

vendor-bom.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ golang.org/x/time;golang time;BSD 3-clause "New" or "Revised" License
5555
google.golang.org/appengine;Golang appengine;Apache License 2.0
5656
google.golang.org/genproto;Google go-genproto;Apache License 2.0
5757
google.golang.org/grpc;grpc grpc-go;Apache License 2.0
58+
gopkg.in/freddierice/go-losetup.v1; Go losetup; MIT License
5859
gopkg.in/fsnotify.v1;fsnotify;BSD 3-clause "New" or "Revised" License
5960
gopkg.in/inf.v0;go-inf;BSD 3-clause "New" or "Revised" License
6061
gopkg.in/square/go-jose.v2;go-jose;Apache License 2.0

0 commit comments

Comments
 (0)