Skip to content

Commit fa34579

Browse files
authored
feat: parse scsi devices form /sys/ (#5335)
1 parent 1921c5b commit fa34579

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

providers/os/connection/device/linux/lun.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ package linux
66
import (
77
"fmt"
88
"io"
9+
"os"
10+
"path"
11+
"path/filepath"
912
"strconv"
1013
"strings"
1114

@@ -22,6 +25,49 @@ type scsiDeviceInfo struct {
2225
type scsiDevices = []scsiDeviceInfo
2326

2427
func (c *LinuxDeviceManager) listScsiDevices() ([]scsiDeviceInfo, error) {
28+
devices, err := c.listScsiDevicesFromSys()
29+
if err == nil {
30+
return devices, nil
31+
}
32+
33+
log.Warn().Err(err).Msg("failed to list scsi devices from sys, trying lsscsi")
34+
return c.listScsiDevicesFromCLI()
35+
}
36+
37+
func (c *LinuxDeviceManager) listScsiDevicesFromSys() ([]scsiDeviceInfo, error) {
38+
blocks, err := os.ReadDir("/sys/block/")
39+
if err != nil {
40+
return nil, err
41+
}
42+
scsiDevices := []scsiDeviceInfo{}
43+
for _, block := range blocks {
44+
if block.Type() != os.ModeSymlink {
45+
continue
46+
}
47+
48+
entry, err := os.Readlink(path.Join("/sys/block/", block.Name()))
49+
if err != nil {
50+
log.Warn().Err(err).Str("block", block.Name()).Msg("failed to readlink")
51+
continue
52+
}
53+
entry, err = filepath.Abs(path.Join("/sys/block/", entry))
54+
if err != nil {
55+
log.Warn().Err(err).Str("block", block.Name()).Msg("failed to get absolute path")
56+
continue
57+
}
58+
59+
device, err := parseScsiDevicePath(entry)
60+
if err != nil {
61+
log.Debug().Err(err).Str("block", block.Name()).Msg("failed to parse device path")
62+
continue
63+
}
64+
scsiDevices = append(scsiDevices, device)
65+
}
66+
67+
return scsiDevices, nil
68+
}
69+
70+
func (c *LinuxDeviceManager) listScsiDevicesFromCLI() ([]scsiDeviceInfo, error) {
2571
cmd, err := c.cmdRunner.RunCommand("lsscsi --brief")
2672
if err != nil {
2773
return nil, err
@@ -83,3 +129,29 @@ func parseLsscsiOutput(output string) (scsiDevices, error) {
83129

84130
return mountedDevices, nil
85131
}
132+
133+
// parses the device path from the sysfs block device path, e.g. /sys/devices/pci0000:00/0000:00:14.0/usb1/1-1/1-1:1.0/host2/target2:0:0/2:0:0:0/block/sdb
134+
// and returns the LUN and the path to the device, e.g. 0 and /dev/sdb
135+
func parseScsiDevicePath(entry string) (res scsiDeviceInfo, err error) {
136+
chunks := strings.Split(entry, "/")
137+
if len(chunks) < 5 {
138+
return res, fmt.Errorf("unexpected entry: %s", entry)
139+
}
140+
141+
if chunks[len(chunks)-2] != "block" {
142+
return res, fmt.Errorf("unexpected entry, expected block: %s", entry)
143+
}
144+
145+
res.VolumePath = path.Join("/dev", chunks[len(chunks)-1])
146+
hbtl := strings.Split(chunks[len(chunks)-3], ":")
147+
if len(hbtl) != 4 {
148+
return res, fmt.Errorf("unexpected entry, expected 4 fields for H:B:T:L: %s", entry)
149+
}
150+
151+
res.Lun, err = strconv.Atoi(hbtl[3])
152+
if err != nil {
153+
return res, fmt.Errorf("unexpected entry, expected integer for LUN: %s", entry)
154+
}
155+
156+
return res, nil
157+
}

providers/os/connection/device/linux/lun_test.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/stretchr/testify/assert"
1010
)
1111

12-
func TestParseLsscsiOutput(t *testing.T) {
12+
func TestParseLsscsiCLIOutput(t *testing.T) {
1313
// different padding for the device names on purpose + an extra blank line
1414
output := `
1515
[0:0:0:0] /dev/sda
@@ -30,6 +30,52 @@ func TestParseLsscsiOutput(t *testing.T) {
3030
assert.ElementsMatch(t, expected, devices)
3131
}
3232

33+
func TestParseScsiPath(t *testing.T) {
34+
tests := []struct {
35+
name string
36+
path string
37+
expected scsiDeviceInfo
38+
err bool
39+
}{
40+
{
41+
name: "valid path",
42+
path: "/sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:00/f8b3781a-1e82-4818-a1c3-63d806ec15bb/host0/target0:0:0/0:0:0:0/block/sda",
43+
expected: scsiDeviceInfo{
44+
Lun: 0,
45+
VolumePath: "/dev/sda",
46+
},
47+
},
48+
{
49+
name: "invalid path (short)",
50+
path: "/sys/devices/no-op",
51+
err: true,
52+
},
53+
{
54+
name: "invalid path (not a block)",
55+
path: "/sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0004:00/VMBUS:00/f8b3781a-1e82-4818-a1c3-63d806ec15bb/host0/target0:0:0/0:0:0:0",
56+
err: true,
57+
},
58+
{
59+
name: "invalid path (invalid H:B:T:L)",
60+
path: "/sys/devices/virtual/block/loop0",
61+
err: true,
62+
expected: scsiDeviceInfo{VolumePath: "/dev/loop0"},
63+
},
64+
}
65+
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
res, err := parseScsiDevicePath(tt.path)
69+
if tt.err {
70+
assert.Error(t, err)
71+
} else {
72+
assert.NoError(t, err)
73+
}
74+
assert.Equal(t, tt.expected, res)
75+
})
76+
}
77+
}
78+
3379
func TestFilterScsiDevices(t *testing.T) {
3480
t.Run("single device", func(t *testing.T) {
3581
devices := scsiDevices{

0 commit comments

Comments
 (0)