Skip to content

Commit

Permalink
✨ Simplify retreival of devices via logical unit numbers.
Browse files Browse the repository at this point in the history
Signed-off-by: Preslav <[email protected]>
  • Loading branch information
preslavgerchev committed Jan 30, 2025
1 parent 8f5e9f1 commit bb752ac
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 167 deletions.
66 changes: 41 additions & 25 deletions providers/os/connection/device/linux/device_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

const (
LunOption = "lun"
LunsOption = "luns"
DeviceName = "device-name"
DeviceNames = "device-names"
MountAllPartitions = "mount-all-partitions"
Expand Down Expand Up @@ -56,20 +57,22 @@ func (d *LinuxDeviceManager) IdentifyMountTargets(opts map[string]string) ([]*sn
if err := validateOpts(opts); err != nil {
return nil, err
}
if opts[LunOption] != "" {
lun, err := strconv.Atoi(opts[LunOption])

deviceNames := []string{}
luns, err := getLunsFromOpts(opts)
if err != nil {
return nil, err
}
for _, l := range luns {
devices, err := d.identifyDeviceViaLun(l)
if err != nil {
return nil, err
}
pi, err := d.identifyViaLun(lun)
if err != nil {
return nil, err
for _, device := range devices {
deviceNames = append(deviceNames, device.Name)
}

return d.attemptExpandPartitions(pi)
}

var deviceNames []string
if opts[DeviceNames] != "" {
deviceNames = strings.Split(opts[DeviceNames], ",")
}
Expand Down Expand Up @@ -359,7 +362,29 @@ func validateOpts(opts map[string]string) error {
return nil
}

func (c *LinuxDeviceManager) identifyViaLun(lun int) ([]*snapshot.PartitionInfo, error) {
func getLunsFromOpts(opts map[string]string) ([]int, error) {
luns := []int{}
if opts[LunOption] != "" {
lun, err := strconv.Atoi(opts[LunOption])
if err != nil {
return nil, err
}
luns = append(luns, lun)
}
if opts[LunsOption] != "" {
vals := strings.Split(opts[LunsOption], ",")
for _, l := range vals {
lun, err := strconv.Atoi(l)
if err != nil {
return nil, err
}
luns = append(luns, lun)
}
}
return luns, nil
}

func (c *LinuxDeviceManager) identifyDeviceViaLun(lun int) ([]snapshot.BlockDevice, error) {
scsiDevices, err := c.listScsiDevices()
if err != nil {
return nil, err
Expand All @@ -370,23 +395,14 @@ func (c *LinuxDeviceManager) identifyViaLun(lun int) ([]*snapshot.PartitionInfo,
if len(filteredScsiDevices) == 0 {
return nil, errors.New("no matching scsi devices found")
}
blockDevices, err := c.volumeMounter.CmdRunner.GetBlockDevices()
if err != nil {
return nil, err
}
var device snapshot.BlockDevice
var deviceErr error
// if we have exactly one device present at the LUN we can directly search for it
if len(filteredScsiDevices) == 1 {
devicePath := filteredScsiDevices[0].VolumePath
device, deviceErr = blockDevices.FindDevice(devicePath)
} else {
device, deviceErr = findMatchingDeviceByBlock(filteredScsiDevices, blockDevices)
}
if deviceErr != nil {
return nil, deviceErr
devices := []snapshot.BlockDevice{}
for _, device := range filteredScsiDevices {
d := snapshot.BlockDevice{
Name: device.VolumePath,
}
devices = append(devices, d)
}
return device.GetPartitions(false, false)
return devices, nil
}

func (c *LinuxDeviceManager) identifyViaDeviceName(deviceName string, mountAll bool, includeMounted bool) ([]*snapshot.PartitionInfo, error) {
Expand Down
39 changes: 0 additions & 39 deletions providers/os/connection/device/linux/lun.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import (
"strconv"
"strings"

"github.com/cockroachdb/errors"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery/v11/providers/os/connection/snapshot"
)

type scsiDeviceInfo struct {
Expand Down Expand Up @@ -54,43 +52,6 @@ func filterScsiDevices(scsiDevices scsiDevices, lun int) []scsiDeviceInfo {
return matching
}

// there can be multiple devices mounted at the same LUN.
// the LUN so we need to find all the blocks, mounted at that LUN. then we find the first one
// that has no mounted partitions and use that as the target device. this is a best-effort approach
func findMatchingDeviceByBlock(scsiDevices scsiDevices, blockDevices *snapshot.BlockDevices) (snapshot.BlockDevice, error) {
matchingBlocks := []snapshot.BlockDevice{}
for _, device := range scsiDevices {
for _, block := range blockDevices.BlockDevices {
devName := "/dev/" + block.Name
if devName == device.VolumePath {
matchingBlocks = append(matchingBlocks, block)
}
}
}

if len(matchingBlocks) == 0 {
return snapshot.BlockDevice{}, errors.New("no matching blocks found")
}

var matching *snapshot.BlockDevice
for _, b := range matchingBlocks {
log.Debug().Str("name", b.Name).Msg("device connection> checking block")
for _, ch := range b.Children {
if len(ch.MountPoint) > 0 && ch.MountPoint != "" {
log.Debug().Str("name", ch.Name).Str("mountpoint", ch.MountPoint).Str("parent", b.Name).Msg("device connection> has mounted partitions, skipping parent block")
break
}
matching = &b
}
}

if matching != nil {
return *matching, nil
}

return snapshot.BlockDevice{}, errors.New("no matching block found")
}

// parses the output from running 'lsscsi --brief' and gets the device info, the output looks like this:
// [0:0:0:0] /dev/sda
// [1:0:0:0] /dev/sdb
Expand Down
132 changes: 29 additions & 103 deletions providers/os/connection/device/linux/lun_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"go.mondoo.com/cnquery/v11/providers/os/connection/snapshot"
)

func TestParseLsscsiOutput(t *testing.T) {
Expand All @@ -32,113 +31,40 @@ func TestParseLsscsiOutput(t *testing.T) {
}

func TestFilterScsiDevices(t *testing.T) {
devices := scsiDevices{
{Lun: 0, VolumePath: "/dev/sda"},
{Lun: 1, VolumePath: "/dev/sdb"},
{Lun: 2, VolumePath: "/dev/sdc"},
{Lun: 3, VolumePath: "/dev/sdd"},
}

filtered := filterScsiDevices(devices, 1)
expected := scsiDevices{
{Lun: 1, VolumePath: "/dev/sdb"},
}
assert.ElementsMatch(t, expected, filtered)

filtered = filterScsiDevices(devices, 4)
assert.Len(t, filtered, 0)
}
t.Run("single device", func(t *testing.T) {
devices := scsiDevices{
{Lun: 0, VolumePath: "/dev/sda"},
{Lun: 1, VolumePath: "/dev/sdb"},
{Lun: 2, VolumePath: "/dev/sdc"},
{Lun: 3, VolumePath: "/dev/sdd"},
}

func TestFindDeviceByBlock(t *testing.T) {
devices := scsiDevices{
{Lun: 0, VolumePath: "/dev/sda"},
{Lun: 0, VolumePath: "/dev/sdb"},
}
t.Run("find device by block", func(t *testing.T) {
blockDevices := &snapshot.BlockDevices{
BlockDevices: []snapshot.BlockDevice{
{
Name: "sda",
Children: []snapshot.BlockDevice{
{
Name: "sda1",
MountPoint: "/",
},
{
Name: "sda2",
},
},
},
{
Name: "sdb",
Children: []snapshot.BlockDevice{
{
Name: "sdb1",
MountPoint: "",
},
},
},
},
filtered := filterScsiDevices(devices, 1)
expected := scsiDevices{
{Lun: 1, VolumePath: "/dev/sdb"},
}
target, err := findMatchingDeviceByBlock(devices, blockDevices)
assert.NoError(t, err)
expected := blockDevices.BlockDevices[1]
assert.Equal(t, expected, target)
assert.ElementsMatch(t, expected, filtered)

filtered = filterScsiDevices(devices, 4)
assert.Len(t, filtered, 0)
})

t.Run("no matches", func(t *testing.T) {
blockDevices := &snapshot.BlockDevices{
BlockDevices: []snapshot.BlockDevice{
{
Name: "sdc",
Children: []snapshot.BlockDevice{
{
Name: "sdc1",
MountPoint: "/",
},
},
},
{
Name: "sdc",
Children: []snapshot.BlockDevice{
{
Name: "sdc1",
MountPoint: "/tmp",
},
},
},
},
t.Run("multiple devices", func(t *testing.T) {
devices := scsiDevices{
{Lun: 0, VolumePath: "/dev/sda"},
{Lun: 1, VolumePath: "/dev/sdb"},
{Lun: 1, VolumePath: "/dev/sdc"},
{Lun: 3, VolumePath: "/dev/sdd"},
}
_, err := findMatchingDeviceByBlock(devices, blockDevices)
assert.Error(t, err)
})
t.Run("empty target as all blocks are mounted", func(t *testing.T) {
blockDevices := &snapshot.BlockDevices{
BlockDevices: []snapshot.BlockDevice{
{
Name: "sda",
Children: []snapshot.BlockDevice{
{
Name: "sda1",
MountPoint: "/",
},
{
Name: "sda2",
},
},
},
{
Name: "sdb",
Children: []snapshot.BlockDevice{
{
Name: "sdb1",
MountPoint: "/tmp",
},
},
},
},

filtered := filterScsiDevices(devices, 1)
expected := scsiDevices{
{Lun: 1, VolumePath: "/dev/sdb"},
{Lun: 1, VolumePath: "/dev/sdc"},
}
_, err := findMatchingDeviceByBlock(devices, blockDevices)
assert.Error(t, err)
assert.ElementsMatch(t, expected, filtered)

filtered = filterScsiDevices(devices, 4)
assert.Len(t, filtered, 0)
})
}

0 comments on commit bb752ac

Please sign in to comment.