Skip to content

Commit

Permalink
⭐️ detect logical volume and block device
Browse files Browse the repository at this point in the history
Most modern Linux distributions use
[Logical Volume Manager (LVM)](https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux))
as a device mapper, in such cases, we need to guess
the block device so that we can mount the attached
disk, if not, we will get the error:

```
DBG found block device children=[{"fstype":"vfat","name":"sdd1","uuid":"6BDD-84EA"},{"fstype":"xfs","label":"boot","name":"sdd2","uuid":"9dd560e5-b943-4e7f-b05f-e79021d9b117"},{"name":"sdd3"},{"name":"sdd4"},{"children":[{"fstype":"xfs","name":"rocky-root","uuid":"73976867-73e1-4771-8799-d725d54fa440"}],"fstype":"LVM2_member","name":"sdd5","uuid":"VtYqiy-LKfB-HUNG-1JMw-z1J2-erkv-J1DxAs"}] mountpoint= name=sdd
DBG found match
DBG found target volume device name=/dev/sdd5
DBG mount volume to scan dir device=/dev/sdd5 fstype=LVM2_member opts= scandir=/tmp/cnspec-scan4160323020
x failed to mount dir error="no such device" attached-fs=/dev/sdd5 fs-type=LVM2_member opts= scan-dir=/tmp/cnspec-scan4160323020
x unable to complete mount step error="no such device"
```

This commit adds a detection for LVM devices, if one is
found, we try to detect the block device and use it to
mount the volume.

Tested on a running VM, works as expected:
```
DBG found block device children=[{"fstype":"vfat","name":"sdd1","uuid":"6BDD-84EA"},{"fstype":"xfs","label":"boot","name":"sdd2","uuid":"9dd560e5-b943-4e7f-b05f-e79021d9b117"},{"name":"sdd3"},{"name":"sdd4"},{"children":[{"fstype":"xfs","name":"rocky-root","uuid":"73976867-73e1-4771-8799-d725d54fa440"}],"fstype":"LVM2_member","name":"sdd5","uuid":"VtYqiy-LKfB-HUNG-1JMw-z1J2-erkv-J1DxAs"}] mountpoint= name=sdd
DBG found match
DBG found target volume device name=/dev/rocky-root
DBG logical volume detected, getting block device name=/dev/rocky-root uuid=73976867-73e1-4771-8799-d725d54fa440
DBG block device found, setting as new device name device=/dev/mapper/rocky-root
DBG mount volume to scan dir device=/dev/mapper/rocky-root fstype=xfs opts=nouuid scandir=/tmp/cnspec-scan3433523306
DBG load filesystem path=/tmp/cnspec-scan3433523306
```

Signed-off-by: Salim Afiune Maya <afiune@mondoo.com>
  • Loading branch information
afiune committed May 23, 2024
1 parent 4a51d90 commit 7842be0
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 16 deletions.
38 changes: 32 additions & 6 deletions providers/os/connection/snapshot/blockdevices.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type BlockDevice struct {
type fsInfo struct {
Name string
FsType string
UUID string
LVM bool
}

func (cmdRunner *LocalCommandRunner) GetBlockDevices() (*BlockDevices, error) {
Expand Down Expand Up @@ -59,7 +61,9 @@ func (blockEntries BlockDevices) GetRootBlockEntry() (*fsInfo, error) {
log.Debug().Msg("get root block entry")
for i := range blockEntries.BlockDevices {
d := blockEntries.BlockDevices[i]
log.Debug().Str("name", d.Name).Interface("children", d.Children).Interface("mountpoint", d.MountPoint).Msg("found block device")
log.Debug().Str("name", d.Name).
Interface("children", d.Children).
Interface("mountpoint", d.MountPoint).Msg("found block device")
for i := range d.Children {
entry := d.Children[i]
if entry.IsNoBootVolume() {
Expand All @@ -81,22 +85,32 @@ func (blockEntries BlockDevices) GetBlockEntryByName(name string) (*fsInfo, erro
}
for i := range blockEntries.BlockDevices {
d := blockEntries.BlockDevices[i]
log.Debug().Str("name", d.Name).Interface("children", d.Children).Interface("mountpoint", d.MountPoint).Msg("found block device")
log.Debug().Str("name", d.Name).
Interface("children", d.Children).
Interface("mountpoint", d.MountPoint).
Msg("found block device")
fullDeviceName := "/dev/" + d.Name
if name != fullDeviceName { // check if the device name matches
if secondName == "" {
continue
}
if secondName != fullDeviceName { // check if the device name matches the second name option (sdh and xvdh are interchangeable)
if secondName != fullDeviceName { // check if the device name matches the second name option (sdh/xvdh)
continue
}
}
log.Debug().Msg("found match")
for i := range d.Children {
entry := d.Children[i]
if entry.IsNotBootOrRootVolumeAndUnmounted() {
devFsName := "/dev/" + entry.Name
return &fsInfo{Name: devFsName, FsType: entry.FsType}, nil
if strings.Contains(entry.FsType, "LVM2") && len(entry.Children) > 0 {
return &fsInfo{
Name: "/dev/" + entry.Children[0].Name,
FsType: entry.Children[0].FsType,
UUID: entry.Children[0].Uuid,
LVM: true,
}, nil
}
return &fsInfo{Name: "/dev/" + entry.Name, FsType: entry.FsType}, nil
}
}
}
Expand All @@ -123,7 +137,10 @@ func (blockEntries BlockDevices) GetUnmountedBlockEntry() (*fsInfo, error) {
log.Debug().Msg("get unmounted block entry")
for i := range blockEntries.BlockDevices {
d := blockEntries.BlockDevices[i]
log.Debug().Str("name", d.Name).Interface("children", d.Children).Interface("mountpoint", d.MountPoint).Msg("found block device")
log.Debug().Str("name", d.Name).
Interface("children", d.Children).
Interface("mountpoint", d.MountPoint).
Msg("found block device")
if d.MountPoint != "" { // empty string means it is not mounted
continue
}
Expand All @@ -139,6 +156,15 @@ func findVolume(children []BlockDevice) *fsInfo {
for i := range children {
entry := children[i]
if entry.IsNotBootOrRootVolumeAndUnmounted() {
if strings.Contains(entry.FsType, "LVM2") && len(entry.Children) > 0 {
return &fsInfo{
Name: "/dev/" + entry.Children[0].Name,
FsType: entry.Children[0].FsType,
UUID: entry.Children[0].Uuid,
LVM: true,
}
}

// we are NOT searching for the root volume here, so we can exclude the "sda" and "xvda" volumes
devFsName := "/dev/" + entry.Name
fs = &fsInfo{Name: devFsName, FsType: entry.FsType}
Expand Down
40 changes: 40 additions & 0 deletions providers/os/connection/snapshot/blockdevices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,46 @@ func TestGetRootBlockEntryRhel8(t *testing.T) {
require.Equal(t, fsInfo{FsType: "xfs", Name: "/dev/sdc2"}, *rootFsInfo)
}

func TestGetRootBlockEntryRocky9(t *testing.T) {
data, err := os.ReadFile("./testdata/rocky9_attached.json")
require.NoError(t, err)

blockEntries := BlockDevices{}
err = json.Unmarshal(data, &blockEntries)
require.NoError(t, err)

rootFsInfo, err := blockEntries.GetRootBlockEntry()
require.NoError(t, err)
require.Equal(t, fsInfo{FsType: "ext4", Name: "/dev/sda1"}, *rootFsInfo)

rootFsInfo, err = blockEntries.GetUnnamedBlockEntry()
require.NoError(t, err)
require.Equal(t, fsInfo{
FsType: "xfs",
Name: "/dev/rocky-root",
UUID: "73976867-73e1-4771-8799-d725d54fa440",
LVM: true,
}, *rootFsInfo)
}

func TestGetBlockEntryByNameRocky9(t *testing.T) {
data, err := os.ReadFile("./testdata/rocky9_attached.json")
require.NoError(t, err)

blockEntries := BlockDevices{}
err = json.Unmarshal(data, &blockEntries)
require.NoError(t, err)

fs, err := blockEntries.GetBlockEntryByName("/dev/sdd")
require.NoError(t, err)
require.Equal(t, fsInfo{
FsType: "xfs",
Name: "/dev/rocky-root",
UUID: "73976867-73e1-4771-8799-d725d54fa440",
LVM: true,
}, *fs)
}

func TestGetRootBlockEntryRhelNoLabels(t *testing.T) {
data, err := os.ReadFile("./testdata/rhel8_nolabels.json")
require.NoError(t, err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@
]
}
]
}
}
2 changes: 1 addition & 1 deletion providers/os/connection/snapshot/testdata/rhel8.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
]
}
]
}
}
30 changes: 30 additions & 0 deletions providers/os/connection/snapshot/testdata/rocky9_attached.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"blockdevices": [
{"name":"sda", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null,
"children": [
{"name":"sda1", "fstype":"ext4", "label":"cloudimg-rootfs", "uuid":"32c589b5-7835-4684-9994-62e8fb26cd85", "fsavail":"26.9G", "fsuse%":"7%", "mountpoint":"/"},
{"name":"sda14", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null},
{"name":"sda15", "fstype":"vfat", "label":"UEFI", "uuid":"9915-EF3D", "fsavail":"98.3M", "fsuse%":"6%", "mountpoint":"/boot/efi"}
]
},
{"name":"sdb", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null,
"children": [
{"name":"sdb1", "fstype":"ext4", "label":null, "uuid":"be29f4fb-2bb8-430b-9c2a-f76604ef10c6", "fsavail":"3.7G", "fsuse%":"0%", "mountpoint":"/mnt"}
]
},
{"name":"sdd", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null,
"children": [
{"name":"sdd1", "fstype":"vfat", "label":null, "uuid":"6BDD-84EA", "fsavail":null, "fsuse%":null, "mountpoint":null},
{"name":"sdd2", "fstype":"xfs", "label":"boot", "uuid":"9dd560e5-b943-4e7f-b05f-e79021d9b117", "fsavail":null, "fsuse%":null, "mountpoint":null},
{"name":"sdd3", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null},
{"name":"sdd4", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null},
{"name":"sdd5", "fstype":"LVM2_member", "label":null, "uuid":"VtYqiy-LKfB-HUNG-1JMw-z1J2-erkv-J1DxAs", "fsavail":null, "fsuse%":null, "mountpoint":null,
"children": [
{"name":"rocky-root", "fstype":"xfs", "label":null, "uuid":"73976867-73e1-4771-8799-d725d54fa440", "fsavail":null, "fsuse%":null, "mountpoint":null}
]
}
]
},
{"name":"sr0", "fstype":null, "label":null, "uuid":null, "fsavail":null, "fsuse%":null, "mountpoint":null}
]
}
43 changes: 35 additions & 8 deletions providers/os/connection/snapshot/volumemounter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package snapshot

import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
Expand Down Expand Up @@ -91,13 +92,13 @@ func (m *VolumeMounter) getFsInfo() (*fsInfo, error) {
fsInfo, err = blockEntries.GetBlockEntryByName(m.VolumeAttachmentLoc)
if err == nil && fsInfo != nil {
return fsInfo, nil
} else {
// if we get here, we couldn't find a fs loaded at the expected location
// AWS does not guarantee this, so that's expected. fallback to find non-boot and non-mounted volume
fsInfo, err = blockEntries.GetUnmountedBlockEntry()
if err == nil && fsInfo != nil {
return fsInfo, nil
}
}

// if we get here, we couldn't find a fs loaded at the expected location
// AWS does not guarantee this, so that's expected. fallback to find non-boot and non-mounted volume
fsInfo, err = blockEntries.GetUnmountedBlockEntry()
if err == nil && fsInfo != nil {
return fsInfo, nil
}
return nil, err
}
Expand All @@ -107,8 +108,34 @@ func (m *VolumeMounter) mountVolume(fsInfo *fsInfo) error {
if fsInfo.FsType == "xfs" {
opts = append(opts, "nouuid")
}

if fsInfo.LVM {
log.Debug().
Str("name", fsInfo.Name).
Str("uuid", fsInfo.UUID).
Msg("logical volume detected, getting block device")
cmd, err := m.CmdRunner.RunCommand(fmt.Sprintf("sudo blkid --uuid %s", fsInfo.UUID))
if err != nil {
log.Debug().Err(err).Msg("unable to detect block device from logical volume")
return err
}
data, err := io.ReadAll(cmd.Stdout)
if err != nil {
return err
}
fsInfo.Name = strings.Trim(string(data), "\t\n")
log.Debug().
Str("device", fsInfo.Name).
Msg("block device found, setting as new device name")
}

opts = stringx.DedupStringArray(opts)
log.Debug().Str("fstype", fsInfo.FsType).Str("device", fsInfo.Name).Str("scandir", m.ScanDir).Str("opts", strings.Join(opts, ",")).Msg("mount volume to scan dir")
log.Debug().
Str("fstype", fsInfo.FsType).
Str("device", fsInfo.Name).
Str("scandir", m.ScanDir).
Str("opts", strings.Join(opts, ",")).
Msg("mount volume to scan dir")
return Mount(fsInfo.Name, m.ScanDir, fsInfo.FsType, opts)
}

Expand Down

0 comments on commit 7842be0

Please sign in to comment.