diff --git a/providers/os/connection/snapshot/blockdevices.go b/providers/os/connection/snapshot/blockdevices.go index f7810d8511..9f46fe1a31 100644 --- a/providers/os/connection/snapshot/blockdevices.go +++ b/providers/os/connection/snapshot/blockdevices.go @@ -30,6 +30,8 @@ type BlockDevice struct { type fsInfo struct { Name string FsType string + UUID string + LVM bool } func (cmdRunner *LocalCommandRunner) GetBlockDevices() (*BlockDevices, error) { @@ -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() { @@ -81,13 +85,16 @@ 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 } } @@ -95,8 +102,15 @@ func (blockEntries BlockDevices) GetBlockEntryByName(name string) (*fsInfo, erro 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 } } } @@ -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 } @@ -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} diff --git a/providers/os/connection/snapshot/blockdevices_test.go b/providers/os/connection/snapshot/blockdevices_test.go index b649b1f487..a88384e2b3 100644 --- a/providers/os/connection/snapshot/blockdevices_test.go +++ b/providers/os/connection/snapshot/blockdevices_test.go @@ -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) diff --git a/providers/os/connection/snapshot/testdata/alma9_attached.json b/providers/os/connection/snapshot/testdata/alma9_attached.json index 2fc182dd9e..c9093102fb 100644 --- a/providers/os/connection/snapshot/testdata/alma9_attached.json +++ b/providers/os/connection/snapshot/testdata/alma9_attached.json @@ -113,4 +113,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/providers/os/connection/snapshot/testdata/rhel8.json b/providers/os/connection/snapshot/testdata/rhel8.json index 71eb38e4d9..a04ae1afba 100644 --- a/providers/os/connection/snapshot/testdata/rhel8.json +++ b/providers/os/connection/snapshot/testdata/rhel8.json @@ -13,4 +13,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/providers/os/connection/snapshot/testdata/rocky9_attached.json b/providers/os/connection/snapshot/testdata/rocky9_attached.json new file mode 100644 index 0000000000..ed874d032d --- /dev/null +++ b/providers/os/connection/snapshot/testdata/rocky9_attached.json @@ -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} + ] +} diff --git a/providers/os/connection/snapshot/volumemounter.go b/providers/os/connection/snapshot/volumemounter.go index 8a3c82cb5d..8712c4fa8b 100644 --- a/providers/os/connection/snapshot/volumemounter.go +++ b/providers/os/connection/snapshot/volumemounter.go @@ -5,6 +5,7 @@ package snapshot import ( "encoding/json" + "fmt" "io" "os" "strings" @@ -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 } @@ -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) }