diff --git a/pkg/console/install_panels.go b/pkg/console/install_panels.go index efa11dd70..57100635b 100644 --- a/pkg/console/install_panels.go +++ b/pkg/console/install_panels.go @@ -289,11 +289,13 @@ func getDataDiskOptions(hvstConfig *config.HarvesterConfig) ([]widgets.Option, e } func getDiskOptions() ([]widgets.Option, error) { - output, err := exec.Command("/bin/sh", "-c", `lsblk -r -o NAME,SIZE,TYPE | grep -w disk | cut -d ' ' -f 1,2`).CombinedOutput() + output, err := exec.Command("/bin/sh", "-c", `lsblk -J -o NAME,SIZE,TYPE,WWN,SERIAL`).CombinedOutput() if err != nil { return nil, err } - lines := strings.Split(strings.TrimSuffix(string(output), "\n"), "\n") + + lines, err := identifyUniqueDisks(output) + var options []widgets.Option for _, line := range lines { splits := strings.SplitN(line, " ", 2) diff --git a/pkg/console/util.go b/pkg/console/util.go index 5d0dd3e3e..073f8c355 100644 --- a/pkg/console/util.go +++ b/pkg/console/util.go @@ -986,3 +986,71 @@ func generateEnvAndConfig(g *gocui.Gui, hvstConfig *config.HarvesterConfig) ([]s env = append(env, fmt.Sprintf("HARVESTER_STREAMDISK_CLOUDINIT_URL=%s", userDataURL)) return env, elementalConfig, nil } + +// internal objects to parse lsblk output +type BlockDevices struct { + Disks []Device `json:"blockdevices"` +} + +type Device struct { + Name string `json:"name"` + Size string `json:"size"` + DiskType string `json:"type"` + WWN string `json:"wwn,omitempty"` + Serial string `json:"serial,omitempty"` + Children []Device `json:"children,omitempty"` +} + +func generateDiskEntry(d Device) string { + return fmt.Sprintf("%s %s", d.Name, d.Size) +} + +const ( + diskType = "disk" +) + +// identifyUniqueDisks parses the json output of lsblk and identifies +// unique disks by comparing their child elements +func identifyUniqueDisks(output []byte) ([]string, error) { + var returnDisks []string + disks := &BlockDevices{} + err := json.Unmarshal(output, disks) + if err != nil { + return nil, fmt.Errorf("error unmarshalling lsblk json output: %v", err) + } + + // identify devices which may be unique + dedupMap := make(map[string]Device) + for _, disk := range disks.Disks { + if disk.DiskType == diskType { + // no children present so not a mpath disk + // add to list of disks + if len(disk.Children) == 0 { + returnDisks = append(returnDisks, generateDiskEntry(disk)) + continue + } + // process children to identify unique disks + // childDevices can contain partition info or multipath disk info + // which does not matter since we eventually dedup the names again + for _, childDevice := range disk.Children { + _, ok := dedupMap[childDevice.Name] + if !ok { + dedupMap[childDevice.Name] = disk + } + } + } + } + // devices may have multiple children, as a result same device map appear twice in dedupMap + // as a result we need to remove duplicates of same deviceName again + resultMap := make(map[string]Device) + for _, v := range dedupMap { + resultMap[v.Name] = v + } + + // generate list of disks + for _, v := range resultMap { + returnDisks = append(returnDisks, generateDiskEntry(v)) + } + + return returnDisks, nil +} diff --git a/pkg/console/util_test.go b/pkg/console/util_test.go index b212c5039..1db4c1108 100644 --- a/pkg/console/util_test.go +++ b/pkg/console/util_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/harvester/harvester-installer/pkg/util" ) @@ -231,3 +232,173 @@ func startMockNTPServers(quit chan interface{}) ([]string, error) { } return ntpServers, nil } + +const ( + sampleSerialDiskOutput = ` +{ + "blockdevices": [ + { + "name": "loop0", + "size": "768.1M", + "type": "loop", + "wwn": null, + "serial": null + },{ + "name": "sda", + "size": "250G", + "type": "disk", + "wwn": null, + "serial": "serial-1", + "children": [ + { + "name": "0QEMU_QEMU_HARDDISK_serial-1", + "size": "250G", + "type": "mpath", + "wwn": null, + "serial": null + } + ] + },{ + "name": "sdb", + "size": "250G", + "type": "disk", + "wwn": null, + "serial": "serial-1", + "children": [ + { + "name": "0QEMU_QEMU_HARDDISK_serial-1", + "size": "250G", + "type": "mpath", + "wwn": null, + "serial": null + } + ] + },{ + "name": "sr0", + "size": "5.8G", + "type": "rom", + "wwn": null, + "serial": "QM00001" + } + ] +} +` + + reinstallDisks = ` +{ + "blockdevices": [ + { + "name": "loop0", + "size": "3G", + "type": "loop", + "wwn": null, + "serial": null + },{ + "name": "loop1", + "size": "10G", + "type": "loop", + "wwn": null, + "serial": null + },{ + "name": "sda", + "size": "10G", + "type": "disk", + "wwn": "0x60000000000000000e00000000010001", + "serial": "beaf11", + "children": [ + { + "name": "sda1", + "size": "2.5G", + "type": "part", + "wwn": "0x60000000000000000e00000000010001", + "serial": null + },{ + "name": "sda14", + "size": "4M", + "type": "part", + "wwn": "0x60000000000000000e00000000010001", + "serial": null + },{ + "name": "sda15", + "size": "106M", + "type": "part", + "wwn": "0x60000000000000000e00000000010001", + "serial": null + },{ + "name": "sda16", + "size": "913M", + "type": "part", + "wwn": "0x60000000000000000e00000000010001", + "serial": null + } + ] + },{ + "name": "sr0", + "size": "364K", + "type": "rom", + "wwn": null, + "serial": "QM00001" + },{ + "name": "vda", + "size": "250G", + "type": "disk", + "wwn": null, + "serial": null, + "children": [ + { + "name": "vda1", + "size": "1M", + "type": "part", + "wwn": null, + "serial": null + },{ + "name": "vda2", + "size": "50M", + "type": "part", + "wwn": null, + "serial": null + },{ + "name": "vda3", + "size": "8G", + "type": "part", + "wwn": null, + "serial": null + },{ + "name": "vda4", + "size": "15G", + "type": "part", + "wwn": null, + "serial": null + },{ + "name": "vda5", + "size": "150G", + "type": "part", + "wwn": null, + "serial": null + },{ + "name": "vda6", + "size": "76.9G", + "type": "part", + "wwn": null, + "serial": null + } + ] + } + ] +} +` +) + +func Test_identifyUniqueDisksWithSerialNumber(t *testing.T) { + assert := require.New(t) + result, err := identifyUniqueDisks([]byte(sampleSerialDiskOutput)) + assert.NoError(err, "expected no error while parsing disk data") + assert.Len(result, 1, "expected to find 1 disk only") +} + +func Test_identifyUniqueDisksWithExistingDisks(t *testing.T) { + assert := require.New(t) + result, err := identifyUniqueDisks([]byte(reinstallDisks)) + assert.NoError(err, "expected no error while parsing disk data") + assert.Len(result, 2, "expected to find 1 disk only") +}