From 5ad0984b3eaaa1d1e9573e6efbc2334e65484cfc Mon Sep 17 00:00:00 2001 From: Mikita Iwanowski Date: Wed, 20 Nov 2024 13:20:46 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20multiple=20block=20devices=20suppor?= =?UTF-8?q?t=20(#4892)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add unpartitioned volumes support * feat: device-names option & multiple devices support * test: TestGetMountablePartitions with unpartitioned device * fix: use deprecated flag option --- providers/os/config/config.go | 6 +++ .../connection/device/linux/device_manager.go | 39 +++++++++++++------ .../os/connection/snapshot/blockdevices.go | 3 ++ .../connection/snapshot/blockdevices_test.go | 15 +++++++ providers/os/provider/provider.go | 12 +++++- 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/providers/os/config/config.go b/providers/os/config/config.go index 330e518871..bc3cc75fc6 100644 --- a/providers/os/config/config.go +++ b/providers/os/config/config.go @@ -280,6 +280,12 @@ var Config = plugin.Provider{ Long: "device-name", Type: plugin.FlagType_String, Desc: "The target device to scan, e.g. /dev/sda. Supported only for Linux scanning. Do not use together with --lun or --serial-number", + Option: plugin.FlagOption_Hidden | plugin.FlagOption_Deprecated, + }, + { + Long: "device-names", + Type: plugin.FlagType_List, + Desc: "The target devices to scan, e.g. /dev/sda. Supported only for Linux scanning. Do not use together with --lun or --serial-number", Option: plugin.FlagOption_Hidden, }, { diff --git a/providers/os/connection/device/linux/device_manager.go b/providers/os/connection/device/linux/device_manager.go index 0cfb45ec7b..47c16b115f 100644 --- a/providers/os/connection/device/linux/device_manager.go +++ b/providers/os/connection/device/linux/device_manager.go @@ -5,6 +5,7 @@ package linux import ( "strconv" + "strings" "github.com/cockroachdb/errors" "github.com/rs/zerolog/log" @@ -14,6 +15,7 @@ import ( const ( LunOption = "lun" DeviceName = "device-name" + DeviceNames = "device-names" MountAllPartitions = "mount-all-partitions" ) @@ -53,11 +55,23 @@ func (d *LinuxDeviceManager) IdentifyMountTargets(opts map[string]string) ([]*sn return []*snapshot.PartitionInfo{pi}, nil } - partitions, err := d.identifyViaDeviceName(opts[DeviceName], opts[MountAllPartitions] == "true") - if err != nil { - return nil, err + deviceNames := strings.Split(opts[DeviceNames], ",") + if opts[DeviceName] != "" { + deviceNames = append(deviceNames, opts[DeviceName]) } - return partitions, nil + + var partitions []*snapshot.PartitionInfo + var errs []error + for _, deviceName := range deviceNames { + partitionsForDevice, err := d.identifyViaDeviceName(deviceName, opts[MountAllPartitions] == "true") + if err != nil { + errs = append(errs, err) + continue + } + partitions = append(partitions, partitionsForDevice...) + } + + return partitions, errors.Join(errs...) } func (d *LinuxDeviceManager) Mount(pi *snapshot.PartitionInfo) (string, error) { @@ -86,18 +100,21 @@ func (d *LinuxDeviceManager) UnmountAndClose() { // we cannot have both LUN and device name provided, those are mutually exclusive func validateOpts(opts map[string]string) error { lun := opts[LunOption] - deviceName := opts[DeviceName] + + // this is needed only for the validation purposes + deviceNames := opts[DeviceNames] + opts[DeviceName] + mountAll := opts[MountAllPartitions] == "true" - if lun != "" && deviceName != "" { - return errors.New("both lun and device name provided") + if lun != "" && deviceNames != "" { + return errors.New("both lun and device names provided") } - if lun == "" && deviceName == "" { - return errors.New("either lun or device name must be provided") + if lun == "" && deviceNames == "" { + return errors.New("either lun or device names must be provided") } - if deviceName == "" && mountAll { - return errors.New("mount-all-partitions requires a device name") + if deviceNames == "" && mountAll { + return errors.New("mount-all-partitions requires device names") } return nil diff --git a/providers/os/connection/snapshot/blockdevices.go b/providers/os/connection/snapshot/blockdevices.go index 5eaa96a035..8f6b05c1e2 100644 --- a/providers/os/connection/snapshot/blockdevices.go +++ b/providers/os/connection/snapshot/blockdevices.go @@ -169,6 +169,9 @@ func (device BlockDevice) GetMountablePartitions(includeAll bool) ([]*PartitionI blockDevices := &BlockDevices{ BlockDevices: device.Children, } + if len(blockDevices.BlockDevices) == 0 && device.FsType != "" { + blockDevices.BlockDevices = append(blockDevices.BlockDevices, device) + } // sort the candidates by size, so we can pick the largest one sortBlockDevicesBySize(blockDevices.BlockDevices) diff --git a/providers/os/connection/snapshot/blockdevices_test.go b/providers/os/connection/snapshot/blockdevices_test.go index 0800636b73..a42dd2a4f3 100644 --- a/providers/os/connection/snapshot/blockdevices_test.go +++ b/providers/os/connection/snapshot/blockdevices_test.go @@ -285,6 +285,21 @@ func TestGetMountablePartitions(t *testing.T) { } require.ElementsMatch(t, expected, parts) }) + + t.Run("get all non-mounted partitions (unpartitioned)", func(t *testing.T) { + block := BlockDevice{ + Name: "sda", + FsType: "xfs", + Label: "ROOT", + Uuid: "1234", + } + parts, err := block.GetMountablePartitions(true) + require.NoError(t, err) + expected := []*PartitionInfo{ + {Name: "/dev/sda", FsType: "xfs", Uuid: "1234", Label: "ROOT"}, + } + require.ElementsMatch(t, expected, parts) + }) } func TestLongestMatchingSuffix(t *testing.T) { diff --git a/providers/os/provider/provider.go b/providers/os/provider/provider.go index c99e5c8261..7bcfeb22ce 100644 --- a/providers/os/provider/provider.go +++ b/providers/os/provider/provider.go @@ -227,9 +227,19 @@ func (s *Service) ParseCLI(req *plugin.ParseCLIReq) (*plugin.ParseCLIRes, error) if lun, ok := flags["lun"]; ok { conf.Options["lun"] = lun.RawData().Value.(string) } + + deviceNames := []string{} if deviceName, ok := flags["device-name"]; ok { - conf.Options["device-name"] = deviceName.RawData().Value.(string) + deviceNames = append(deviceNames, deviceName.RawData().Value.(string)) + } + if deviceName, ok := flags["device-names"]; ok { + deviceNamesList := deviceName.RawData().Value.([]any) + for _, deviceName := range deviceNamesList { + deviceNames = append(deviceNames, deviceName.(string)) + } } + conf.Options["device-names"] = strings.Join(deviceNames, ",") + if serialNumber, ok := flags["serial-number"]; ok { conf.Options["serial-number"] = serialNumber.RawData().Value.(string) }