This patch optimizes unnecessary calls to the lvs command when running
gnt-cluster verify (among other operations) by caching multiple series
of lvs statements into a single operation and storing the results in
a dictionary with device paths used as keys.

Signed-off-by: Federico Morg Pareschi <m...@google.com>
---
 lib/backend.py                          |  27 ++++++--
 lib/storage/base.py                     |   4 +-
 lib/storage/bdev.py                     |  94 ++++++++++++++-------------
 lib/storage/drbd.py                     |   4 +-
 lib/storage/extstorage.py               |   7 +-
 lib/storage/filestorage.py              |   4 +-
 lib/storage/gluster.py                  |   4 +-
 test/py/ganeti.storage.bdev_unittest.py | 110 ++++++++++++++------------------
 8 files changed, 132 insertions(+), 122 deletions(-)

diff --git a/lib/backend.py b/lib/backend.py
index 56b5b18..ba71fbe 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -3804,9 +3804,13 @@ def BlockdevGetmirrorstatusMulti(disks):
 
   """
   result = []
+  lvs_cache = None
+  is_plain_disk = any([_CheckForPlainDisk(d) for d in disks])
+  if is_plain_disk:
+    lvs_cache = bdev.LogicalVolume._GetLvGlobalInfo()
   for disk in disks:
     try:
-      rbd = _RecursiveFindBD(disk)
+      rbd = _RecursiveFindBD(disk, lvs_cache=lvs_cache)
       if rbd is None:
         result.append((False, "Can't find device %s" % disk))
         continue
@@ -3823,7 +3827,22 @@ def BlockdevGetmirrorstatusMulti(disks):
   return result
 
 
-def _RecursiveFindBD(disk):
+def _CheckForPlainDisk(disk, found=False):
+  """Check within a disk and its children if there is a plain disk type.
+
+  @type disk: L{objects.Disk}
+  @param disk: the disk we are checking
+  @rtype: bool
+  @return: whether or not there is a plain disk type
+
+  """
+  found |= disk.dev_type == constants.DT_PLAIN
+  if disk.children:
+    return any([_CheckForPlainDisk(d, found) for d in disk.children])
+  return found
+
+
+def _RecursiveFindBD(disk, lvs_cache=None):
   """Check if a device is activated.
 
   If so, return information about the real device.
@@ -3838,9 +3857,9 @@ def _RecursiveFindBD(disk):
   children = []
   if disk.children:
     for chdisk in disk.children:
-      children.append(_RecursiveFindBD(chdisk))
+      children.append(_RecursiveFindBD(chdisk, lvs_cache=lvs_cache))
 
-  return bdev.FindDevice(disk, children)
+  return bdev.FindDevice(disk, children, lvs_cache=lvs_cache)
 
 
 def _OpenRealBD(disk):
diff --git a/lib/storage/base.py b/lib/storage/base.py
index dcafb41..588caf2 100644
--- a/lib/storage/base.py
+++ b/lib/storage/base.py
@@ -82,7 +82,7 @@ class BlockDev(object):
 
   """
   # pylint: disable=W0613
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     self._children = children
     self.dev_path = None
     self.unique_id = unique_id
@@ -121,7 +121,7 @@ class BlockDev(object):
 
   @classmethod
   def Create(cls, unique_id, children, size, spindles, params, excl_stor,
-             dyn_params, *args):
+             dyn_params, **kwargs):
     """Create the device.
 
     If the device cannot be created, it will return None
diff --git a/lib/storage/bdev.py b/lib/storage/bdev.py
index e3a48de..2d97284 100644
--- a/lib/storage/bdev.py
+++ b/lib/storage/bdev.py
@@ -78,14 +78,14 @@ class LogicalVolume(base.BlockDev):
   _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
   _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
 
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     """Attaches to a LV device.
 
     The unique_id is a tuple (vg_name, lv_name)
 
     """
     super(LogicalVolume, self).__init__(unique_id, children, size, params,
-                                        dyn_params, *args)
+                                        dyn_params, **kwargs)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
     self._vg_name, self._lv_name = unique_id
@@ -95,7 +95,12 @@ class LogicalVolume(base.BlockDev):
     self._degraded = True
     self.major = self.minor = self.pe_size = self.stripe_count = None
     self.pv_names = None
-    self.Attach()
+    lvs_cache = kwargs.get("lvs_cache")
+    if lvs_cache:
+      lv_info = lvs_cache.get(self.dev_path)
+      self.Attach(lv_info=lv_info)
+    else:
+      self.Attach()
 
   @staticmethod
   def _GetStdPvSize(pvs_info):
@@ -436,9 +441,9 @@ class LogicalVolume(base.BlockDev):
     self._lv_name = new_name
     self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
 
-  @classmethod
-  def _ParseLvInfoLine(cls, line, sep):
-    """Parse one line of the lvs output used in L{_GetLvInfo}.
+  @staticmethod
+  def _ParseLvInfoLine(line, sep):
+    """Parse one line of the lvs output used in L{_GetLvGlobalInfo}.
 
     """
     elems = line.strip().split(sep)
@@ -447,13 +452,13 @@ class LogicalVolume(base.BlockDev):
     # separator to the right of the output. The PV info might be empty for
     # thin volumes, so stripping off the separators might cut off the last
     # empty element - do this instead.
-    if len(elems) == 7 and elems[-1] == "":
+    if len(elems) == 8 and elems[-1] == "":
       elems.pop()
 
-    if len(elems) != 6:
-      base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
+    if len(elems) != 7:
+      base.ThrowError("Can't parse LVS output, len(%s) != 7", str(elems))
 
-    (status, major, minor, pe_size, stripes, pvs) = elems
+    (path, status, major, minor, pe_size, stripes, pvs) = elems
     if len(status) < 6:
       base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
 
@@ -476,43 +481,41 @@ class LogicalVolume(base.BlockDev):
     pv_names = []
     if pvs != "":
       for pv in pvs.split(","):
-        m = re.match(cls._PARSE_PV_DEV_RE, pv)
+        m = re.match(LogicalVolume._PARSE_PV_DEV_RE, pv)
         if not m:
           base.ThrowError("Can't parse this device list: %s", pvs)
         pv_names.append(m.group(1))
 
-    return (status, major, minor, pe_size, stripes, pv_names)
+    return (path, (status, major, minor, pe_size, stripes, pv_names))
 
-  @classmethod
-  def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd):
-    """Get info about the given existing LV to be used.
+  @staticmethod
+  def _GetLvGlobalInfo(_run_cmd=utils.RunCmd):
+    """Obtain the current state of the existing LV disks.
+
+    @return: a dict containing the state of each disk with the disk path as key
 
     """
     sep = "|"
     result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
                        "--units=k", "--nosuffix",
-                       "-olv_attr,lv_kernel_major,lv_kernel_minor,"
-                       "vg_extent_size,stripes,devices", dev_path])
+                       "-olv_path,lv_attr,lv_kernel_major,lv_kernel_minor,"
+                       "vg_extent_size,stripes,devices"])
     if result.failed:
-      base.ThrowError("Can't find LV %s: %s, %s",
-                      dev_path, result.fail_reason, result.output)
-    # the output can (and will) have multiple lines for multi-segment
-    # LVs, as the 'stripes' parameter is a segment one, so we take
-    # only the last entry, which is the one we're interested in; note
-    # that with LVM2 anyway the 'stripes' value must be constant
-    # across segments, so this is a no-op actually
+      logging.warning("lvs command failed, the LV cache will be empty!")
+      logging.info("lvs failure: %r", result.stderr)
+      return {}
     out = result.stdout.splitlines()
-    if not out: # totally empty result? splitlines() returns at least
-                # one line for any non-empty string
-      base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
-    pv_names = set()
+    if not out:
+      logging.warning("lvs command returned an empty output, the LV cache will"
+                      "be empty!")
+      return {}
+    info = {}
     for line in out:
-      (status, major, minor, pe_size, stripes, more_pvs) = \
-        cls._ParseLvInfoLine(line, sep)
-      pv_names.update(more_pvs)
-    return (status, major, minor, pe_size, stripes, pv_names)
+      (key, value) = LogicalVolume._ParseLvInfoLine(line, sep)
+      info[key] = value
+    return info
 
-  def Attach(self):
+  def Attach(self, lv_info=None):
     """Attach to an existing LV.
 
     This method will try to see if an existing and active LV exists
@@ -521,11 +524,11 @@ class LogicalVolume(base.BlockDev):
 
     """
     self.attached = False
-    try:
+    if not lv_info:
       (status, major, minor, pe_size, stripes, pv_names) = \
-        self._GetLvInfo(self.dev_path)
-    except errors.BlockDeviceError:
-      return False
+          LogicalVolume._GetLvGlobalInfo()[self.dev_path]
+    else:
+      (status, major, minor, pe_size, stripes, pv_names) = lv_info
 
     self.major = major
     self.minor = minor
@@ -747,14 +750,14 @@ class PersistentBlockDevice(base.BlockDev):
   For the time being, pathnames are required to lie under /dev.
 
   """
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     """Attaches to a static block device.
 
     The unique_id is a path under /dev.
 
     """
     super(PersistentBlockDevice, self).__init__(unique_id, children, size,
-                                                params, dyn_params, *args)
+                                                params, dyn_params, **kwargs)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
     self.dev_path = unique_id[1]
@@ -803,7 +806,6 @@ class PersistentBlockDevice(base.BlockDev):
   def Attach(self):
     """Attach to an existing block device.
 
-
     """
     self.attached = False
     try:
@@ -870,12 +872,12 @@ class RADOSBlockDevice(base.BlockDev):
   this to be functional.
 
   """
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     """Attaches to an rbd device.
 
     """
     super(RADOSBlockDevice, self).__init__(unique_id, children, size, params,
-                                           dyn_params, *args)
+                                           dyn_params, **kwargs)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
 
@@ -1305,7 +1307,7 @@ def _VerifyDiskParams(disk):
                                  missing)
 
 
-def FindDevice(disk, children):
+def FindDevice(disk, children, **kwargs):
   """Search for an existing, assembled device.
 
   This will succeed only if the device exists and is assembled, but it
@@ -1321,7 +1323,7 @@ def FindDevice(disk, children):
   _VerifyDiskType(disk.dev_type)
   device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
                                   disk.params, disk.dynamic_params,
-                                  disk.name, disk.uuid)
+                                  name=disk.name, uuid=disk.uuid, **kwargs)
   if not device.attached:
     return None
   return device
@@ -1344,7 +1346,7 @@ def Assemble(disk, children):
   _VerifyDiskParams(disk)
   device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size,
                                   disk.params, disk.dynamic_params,
-                                  disk.name, disk.uuid)
+                                  name=disk.name, uuid=disk.uuid)
   device.Assemble()
   return device
 
@@ -1368,7 +1370,7 @@ def Create(disk, children, excl_stor):
   device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size,
                                          disk.spindles, disk.params, excl_stor,
                                          disk.dynamic_params,
-                                         disk.name, disk.uuid)
+                                         name=disk.name, uuid=disk.uuid)
   return device
 
 # Please keep this at the bottom of the file for visibility.
diff --git a/lib/storage/drbd.py b/lib/storage/drbd.py
index 5c4817b..081e96a 100644
--- a/lib/storage/drbd.py
+++ b/lib/storage/drbd.py
@@ -183,7 +183,7 @@ class DRBD8Dev(base.BlockDev):
   # timeout constants
   _NET_RECONFIG_TIMEOUT = 60
 
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     if children and children.count(None) > 0:
       children = []
     if len(children) not in (0, 2):
@@ -210,7 +210,7 @@ class DRBD8Dev(base.BlockDev):
         logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
         children = []
     super(DRBD8Dev, self).__init__(unique_id, children, size, params,
-                                   dyn_params, *args)
+                                   dyn_params, **kwargs)
     self.major = self._DRBD_MAJOR
 
     info = DRBD8.GetProcInfo()
diff --git a/lib/storage/extstorage.py b/lib/storage/extstorage.py
index 311662f..32f7f45 100644
--- a/lib/storage/extstorage.py
+++ b/lib/storage/extstorage.py
@@ -52,13 +52,14 @@ class ExtStorageDevice(base.BlockDev):
   handling of the externally provided block devices.
 
   """
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     """Attaches to an extstorage block device.
 
     """
     super(ExtStorageDevice, self).__init__(unique_id, children, size, params,
-                                           dyn_params, *args)
-    (self.name, self.uuid) = args
+                                           dyn_params, **kwargs)
+    self.name = kwargs["name"]
+    self.uuid = kwargs["uuid"]
 
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
diff --git a/lib/storage/filestorage.py b/lib/storage/filestorage.py
index 74234e8..ba89975 100644
--- a/lib/storage/filestorage.py
+++ b/lib/storage/filestorage.py
@@ -190,14 +190,14 @@ class FileStorage(base.BlockDev):
   The unique_id for the file device is a (file_driver, file_path) tuple.
 
   """
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     """Initalizes a file device backend.
 
     """
     if children:
       raise errors.BlockDeviceError("Invalid setup for file device")
     super(FileStorage, self).__init__(unique_id, children, size, params,
-                                      dyn_params, *args)
+                                      dyn_params, **kwargs)
     if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
       raise ValueError("Invalid configuration data %s" % str(unique_id))
     self.driver = unique_id[0]
diff --git a/lib/storage/gluster.py b/lib/storage/gluster.py
index 6418c9d..6aab127 100644
--- a/lib/storage/gluster.py
+++ b/lib/storage/gluster.py
@@ -293,7 +293,7 @@ class GlusterStorage(base.BlockDev):
   The unique_id for the file device is a (file_driver, file_path) tuple.
 
   """
-  def __init__(self, unique_id, children, size, params, dyn_params, *args):
+  def __init__(self, unique_id, children, size, params, dyn_params, **kwargs):
     """Initalizes a file device backend.
 
     """
@@ -316,7 +316,7 @@ class GlusterStorage(base.BlockDev):
     self.file = None
 
     super(GlusterStorage, self).__init__(unique_id, children, size,
-                                         params, dyn_params, *args)
+                                         params, dyn_params, **kwargs)
 
     self.Attach()
 
diff --git a/test/py/ganeti.storage.bdev_unittest.py 
b/test/py/ganeti.storage.bdev_unittest.py
index 8b6106c..89f3b49 100755
--- a/test/py/ganeti.storage.bdev_unittest.py
+++ b/test/py/ganeti.storage.bdev_unittest.py
@@ -239,38 +239,43 @@ class TestLogicalVolume(unittest.TestCase):
   def testParseLvInfoLine(self):
     """Tests for LogicalVolume._ParseLvInfoLine."""
     broken_lines = [
-      "  toomuch#-wi-ao#253#3#4096.00#2#/dev/abc(20)",
-      "  -wi-ao#253#3#4096.00#/dev/abc(20)",
-      "  -wi-a#253#3#4096.00#2#/dev/abc(20)",
-      "  -wi-ao#25.3#3#4096.00#2#/dev/abc(20)",
-      "  -wi-ao#twenty#3#4096.00#2#/dev/abc(20)",
-      "  -wi-ao#253#3.1#4096.00#2#/dev/abc(20)",
-      "  -wi-ao#253#three#4096.00#2#/dev/abc(20)",
-      "  -wi-ao#253#3#four#2#/dev/abc(20)",
-      "  -wi-ao#253#3#4096..00#2#/dev/abc(20)",
-      "  -wi-ao#253#3#4096.00#2.0#/dev/abc(20)",
-      "  -wi-ao#253#3#4096.00#two#/dev/abc(20)",
+      "  toomuch#devpath#-wi-ao#253#3#4096.00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3#4096.00#/dev/abc(20)",
+      "  devpath#-wi-a#253#3#4096.00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#25.3#3#4096.00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#twenty#3#4096.00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3.1#4096.00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#253#three#4096.00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3#four#2#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3#4096..00#2#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3#4096.00#2.0#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3#4096.00#two#/dev/abc(20)",
+      "  devpath#-wi-ao#253#3#4096.00#2#/dev/abc20",
       ]
     for broken in broken_lines:
       self.assertRaises(errors.BlockDeviceError,
                         bdev.LogicalVolume._ParseLvInfoLine, broken, "#")
 
     # Examples of good lines from "lvs":
-    #  -wi-ao|253|3|4096.00|2|/dev/sdb(144),/dev/sdc(0)
-    #  -wi-a-|253|4|4096.00|1|/dev/sdb(208)
+    #
+    #   /dev/something|-wi-ao|253|3|4096.00|2|/dev/sdb(144),/dev/sdc(0)
+    #   /dev/somethingelse|-wi-a-|253|4|4096.00|1|/dev/sdb(208)
     true_out = [
-      ("-wi-ao", 253, 3, 4096.00, 2, ["/dev/abc"]),
-      ("-wi-a-", 253, 7, 4096.00, 4, ["/dev/abc"]),
-      ("-ri-a-", 253, 4, 4.00, 5, ["/dev/abc", "/dev/def"]),
-      ("-wc-ao", 15, 18, 4096.00, 32, ["/dev/abc", "/dev/def", "/dev/ghi0"]),
-      # Physical devices might be missing with thin volumes
-      ("twc-ao", 15, 18, 4096.00, 32, []),
-      ]
+        ("/dev/path", ("-wi-ao", 253, 3, 4096.00, 2, ["/dev/abc"])),
+        ("/dev/path", ("-wi-a-", 253, 7, 4096.00, 4, ["/dev/abc"])),
+        ("/dev/path", ("-ri-a-", 253, 4, 4.00, 5, ["/dev/abc", "/dev/def"])),
+        ("/dev/path", ("-wc-ao", 15, 18, 4096.00, 32,
+                       ["/dev/abc", "/dev/def", "/dev/ghi0"])),
+        # Physical devices might be missing with thin volumes
+        ("/dev/path", ("twc-ao", 15, 18, 4096.00, 32, [])),
+    ]
     for exp in true_out:
       for sep in "#;|":
-        pvs = ",".join("%s(%s)" % (d, i * 12) for (i, d) in enumerate(exp[-1]))
-        lvs_line = (sep.join(("  %s", "%d", "%d", "%.2f", "%d", "%s")) %
-                    (exp[0:-1] + (pvs,)))
+        devpath = exp[0]
+        lvs = exp[1]
+        pvs = ",".join("%s(%s)" % (d, i * 12) for (i, d) in enumerate(lvs[-1]))
+        lvs_line = (sep.join(("  %s", "%s", "%d", "%d", "%.2f", "%d", "%s")) %
+                    ((devpath,) + lvs[0:-1] + (pvs,)))
         parsed = bdev.LogicalVolume._ParseLvInfoLine(lvs_line, sep)
         self.assertEqual(parsed, exp)
 
@@ -283,44 +288,27 @@ class TestLogicalVolume(unittest.TestCase):
     return lambda cmd: utils.RunResult(exit_code, None, stdout, "", cmd,
                                        utils.process._TIMEOUT_NONE, 5)
 
-  def testGetLvInfo(self):
-    """Tests for LogicalVolume._GetLvInfo."""
-    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
-                      "fake_path",
-                      _run_cmd=self._FakeRunCmd(False, "Fake error msg"))
-    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
-                      "fake_path", _run_cmd=self._FakeRunCmd(True, ""))
-    self.assertRaises(errors.BlockDeviceError, bdev.LogicalVolume._GetLvInfo,
-                      "fake_path", _run_cmd=self._FakeRunCmd(True, 
"BadStdOut"))
-    good_line = "  -wi-ao|253|3|4096.00|2|/dev/abc(20)"
-    fake_cmd = self._FakeRunCmd(True, good_line)
-    good_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
-    # If the same line is repeated, the result should be the same
-    for lines in [
-      [good_line] * 2,
-      [good_line] * 3,
-      ]:
-      fake_cmd = self._FakeRunCmd(True, "\n".join(lines))
-      same_res = bdev.LogicalVolume._GetLvInfo("fake_path", fake_cmd)
-      self.assertEqual(same_res, good_res)
-
-    # Complex multi-line examples
-    one_line = "  -wi-ao|253|3|4096.00|2|/dev/sda(20),/dev/sdb(50),/dev/sdc(0)"
-    fake_cmd = self._FakeRunCmd(True, one_line)
-    one_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
-    # These should give the same results
-    for multi_lines in [
-      ("  -wi-ao|253|3|4096.00|2|/dev/sda(30),/dev/sdb(50)\n"
-       "  -wi-ao|253|3|4096.00|2|/dev/sdb(200),/dev/sdc(300)"),
-      ("  -wi-ao|253|3|4096.00|2|/dev/sda(0)\n"
-       "  -wi-ao|253|3|4096.00|2|/dev/sdb(20)\n"
-       "  -wi-ao|253|3|4096.00|2|/dev/sdc(30)"),
-      ("  -wi-ao|253|3|4096.00|2|/dev/sda(20)\n"
-       "  -wi-ao|253|3|4096.00|2|/dev/sdb(50),/dev/sdc(0)"),
-      ]:
-      fake_cmd = self._FakeRunCmd(True, multi_lines)
-      multi_res = bdev.LogicalVolume._GetLvInfo("fake_path", _run_cmd=fake_cmd)
-      self.assertEqual(multi_res, one_res)
+  def testGetLvGlobalInfo(self):
+    """Tests for LogicalVolume._GetLvGlobalInfo."""
+
+    good_lines="/dev/1|-wi-ao|253|3|4096.00|2|/dev/sda(20)\n" \
+        "/dev/2|-wi-ao|253|3|4096.00|2|/dev/sda(21)\n"
+    expected_output = {"/dev/1": ("-wi-ao", 253, 3, 4096, 2, ["/dev/sda"]),
+                       "/dev/2": ("-wi-ao", 253, 3, 4096, 2, ["/dev/sda"])}
+
+    self.assertEqual({},
+                     bdev.LogicalVolume._GetLvGlobalInfo(
+                         _run_cmd=self._FakeRunCmd(False, "Fake error msg")))
+    self.assertEqual({},
+                     bdev.LogicalVolume._GetLvGlobalInfo(
+                         _run_cmd=self._FakeRunCmd(True, "")))
+    self.assertRaises(errors.BlockDeviceError,
+                      bdev.LogicalVolume._GetLvGlobalInfo,
+                      _run_cmd=self._FakeRunCmd(True, "BadStdOut"))
+
+    fake_cmd = self._FakeRunCmd(True, good_lines)
+    good_res = bdev.LogicalVolume._GetLvGlobalInfo(_run_cmd=fake_cmd)
+    self.assertEqual(expected_output, good_res)
 
   @testutils.patch_object(bdev.LogicalVolume, "Attach")
   def testLogicalVolumeImport(self, attach_mock):
-- 
2.8.0.rc3.226.g39d4020

Reply via email to