Merge branch 'devel-2.7'
[ganeti-local] / lib / bdev.py
index 06c2d2f..b221235 100644 (file)
@@ -542,7 +542,7 @@ class LogicalVolume(BlockDev):
     """Compute the number of PVs needed for an LV (with exclusive storage).
 
     @type size: float
-    @param size: LV size
+    @param size: LV size in MiB
     @param pvs_info: list of objects.LvmPvInfo, cannot be empty
     @rtype: integer
     @return: number of PVs needed
@@ -592,7 +592,7 @@ class LogicalVolume(BlockDev):
     stripes = min(current_pvs, desired_stripes)
 
     if excl_stor:
-      err_msgs = utils.LvmExclusiveCheckNodePvs(pvs_info)
+      (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
       if err_msgs:
         for m in err_msgs:
           logging.warning(m)
@@ -666,43 +666,84 @@ class LogicalVolume(BlockDev):
     return data
 
   @classmethod
-  def GetPVInfo(cls, vg_names, filter_allocatable=True):
+  def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
     """Get the free space info for PVs in a volume group.
 
     @param vg_names: list of volume group names, if empty all will be returned
     @param filter_allocatable: whether to skip over unallocatable PVs
+    @param include_lvs: whether to include a list of LVs hosted on each PV
 
     @rtype: list
     @return: list of objects.LvmPvInfo objects
 
     """
+    # We request "lv_name" field only if we care about LVs, so we don't get
+    # a long list of entries with many duplicates unless we really have to.
+    # The duplicate "pv_name" field will be ignored.
+    if include_lvs:
+      lvfield = "lv_name"
+    else:
+      lvfield = "pv_name"
     try:
       info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
-                                        "pv_attr", "pv_size"])
+                                        "pv_attr", "pv_size", lvfield])
     except errors.GenericError, err:
       logging.error("Can't get PV information: %s", err)
       return None
 
+    # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
+    # pair. We sort entries by PV name and then LV name, so it's easy to weed
+    # out duplicates.
+    if include_lvs:
+      info.sort(key=(lambda i: (i[0], i[5])))
     data = []
-    for (pv_name, vg_name, pv_free, pv_attr, pv_size) in info:
+    lastpvi = None
+    for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
       # (possibly) skip over pvs which are not allocatable
       if filter_allocatable and pv_attr[0] != "a":
         continue
       # (possibly) skip over pvs which are not in the right volume group(s)
       if vg_names and vg_name not in vg_names:
         continue
-      pvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
-                              size=float(pv_size), free=float(pv_free),
-                              attributes=pv_attr)
-      data.append(pvi)
+      # Beware of duplicates (check before inserting)
+      if lastpvi and lastpvi.name == pv_name:
+        if include_lvs and lv_name:
+          if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
+            lastpvi.lv_list.append(lv_name)
+      else:
+        if include_lvs and lv_name:
+          lvl = [lv_name]
+        else:
+          lvl = []
+        lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
+                                    size=float(pv_size), free=float(pv_free),
+                                    attributes=pv_attr, lv_list=lvl)
+        data.append(lastpvi)
 
     return data
 
   @classmethod
-  def GetVGInfo(cls, vg_names, filter_readonly=True):
+  def _GetExclusiveStorageVgFree(cls, vg_name):
+    """Return the free disk space in the given VG, in exclusive storage mode.
+
+    @type vg_name: string
+    @param vg_name: VG name
+    @rtype: float
+    @return: free space in MiB
+    """
+    pvs_info = cls.GetPVInfo([vg_name])
+    if not pvs_info:
+      return 0.0
+    pv_size = cls._GetStdPvSize(pvs_info)
+    num_pvs = len(cls._GetEmptyPvNames(pvs_info))
+    return pv_size * num_pvs
+
+  @classmethod
+  def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
     """Get the free space info for specific VGs.
 
     @param vg_names: list of volume group names, if empty all will be returned
+    @param excl_stor: whether exclusive_storage is enabled
     @param filter_readonly: whether to skip over readonly VGs
 
     @rtype: list
@@ -725,6 +766,11 @@ class LogicalVolume(BlockDev):
       # (possibly) skip over vgs which are not in the right volume group(s)
       if vg_names and vg_name not in vg_names:
         continue
+      # Exclusive storage needs a different concept of free space
+      if excl_stor:
+        es_free = cls._GetExclusiveStorageVgFree(vg_name)
+        assert es_free <= vg_free
+        vg_free = es_free
       data.append((float(vg_free), float(vg_size), vg_name))
 
     return data
@@ -921,7 +967,7 @@ class LogicalVolume(BlockDev):
     snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params)
     _IgnoreError(snap.Remove)
 
-    vg_info = self.GetVGInfo([self._vg_name])
+    vg_info = self.GetVGInfo([self._vg_name], False)
     if not vg_info:
       _ThrowError("Can't compute VG info for vg %s", self._vg_name)
     free_size, _, _ = vg_info[0]