func (s *ListBlockDevicesSuite) testListBlockDevicesExtended( c *gc.C, udevadmInfo string, expect storage.BlockDevice, ) { testing.PatchExecutable(c, s, "lsblk", `#!/bin/bash --norc cat <<EOF KNAME="sda" SIZE="240057409536" LABEL="" UUID="" TYPE="disk" EOF`) testing.PatchExecutable(c, s, "udevadm", `#!/bin/bash --norc cat <<EOF `+strings.TrimSpace(udevadmInfo)+` EOF`) expect.DeviceName = "sda" expect.Size = 228936 devices, err := diskmanager.ListBlockDevices() c.Assert(err, jc.ErrorIsNil) c.Assert(devices, jc.DeepEquals, []storage.BlockDevice{expect}) }
func listBlockDevices() ([]storage.BlockDevice, error) { columns := []string{ "KNAME", // kernel name "SIZE", // size "LABEL", // filesystem label "UUID", // filesystem UUID "FSTYPE", // filesystem type "TYPE", // device type "MOUNTPOINT", // moint point } logger.Tracef("executing lsblk") output, err := exec.Command( "lsblk", "-b", // output size in bytes "-P", // output fields as key=value pairs "-o", strings.Join(columns, ","), ).Output() if err != nil { return nil, errors.Annotate( err, "cannot list block devices: lsblk failed", ) } var devices []storage.BlockDevice s := bufio.NewScanner(bytes.NewReader(output)) for s.Scan() { pairs := pairsRE.FindAllStringSubmatch(s.Text(), -1) var dev storage.BlockDevice var deviceType string for _, pair := range pairs { switch pair[1] { case "KNAME": dev.DeviceName = pair[2] case "SIZE": size, err := strconv.ParseUint(pair[2], 10, 64) if err != nil { logger.Errorf( "invalid size %q from lsblk: %v", pair[2], err, ) } else { dev.Size = size / bytesInMiB } case "LABEL": dev.Label = pair[2] case "UUID": dev.UUID = pair[2] case "FSTYPE": dev.FilesystemType = pair[2] case "TYPE": deviceType = pair[2] case "MOUNTPOINT": dev.MountPoint = pair[2] default: logger.Debugf("unexpected field from lsblk: %q", pair[1]) } } // We may later want to expand this, e.g. to handle lvm, // dmraid, crypt, etc., but this is enough to cover bases // for now. switch deviceType { case typeDisk, typeLoop: default: logger.Tracef("ignoring %q type device: %+v", deviceType, dev) continue } // Check if the block device is in use. We need to know this so we can // issue an error if the user attempts to allocate an in-use disk to a // unit. dev.InUse, err = blockDeviceInUse(dev) if os.IsNotExist(err) { // In LXC containers, lsblk will show the block devices of the // host, but the devices will typically not be present. continue } else if err != nil { logger.Infof("could not check if %q is in use: %v", dev.DeviceName, err) // We cannot detect, so err on the side of caution and default to // "in use" so the device cannot be used. dev.InUse = true } // Add additional information from sysfs. if err := addHardwareInfo(&dev); err != nil { logger.Errorf( "error getting hardware info for %q from sysfs: %v", dev.DeviceName, err, ) } devices = append(devices, dev) } if err := s.Err(); err != nil { return nil, errors.Annotate(err, "cannot parse lsblk output") } return devices, nil }