Fix clearing of the default iallocator
[ganeti-local] / lib / bdev.py
index f2b96f4..1378a2f 100644 (file)
@@ -1,7 +1,7 @@
 #
 #
 
-# Copyright (C) 2006, 2007 Google Inc.
+# Copyright (C) 2006, 2007, 2010 Google Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -33,6 +33,7 @@ from ganeti import errors
 from ganeti import constants
 from ganeti import objects
 from ganeti import compat
+from ganeti import netutils
 
 
 # Size of reads in _CanReadDevice
@@ -417,7 +418,40 @@ class LogicalVolume(BlockDev):
     return LogicalVolume(unique_id, children, size)
 
   @staticmethod
-  def GetPVInfo(vg_names, filter_allocatable=True):
+  def _GetVolumeInfo(lvm_cmd, fields):
+    """Returns LVM Volumen infos using lvm_cmd
+
+    @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
+    @param fields: Fields to return
+    @return: A list of dicts each with the parsed fields
+
+    """
+    if not fields:
+      raise errors.ProgrammerError("No fields specified")
+
+    sep = "|"
+    cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
+           "--separator=%s" % sep, "-o%s" % ",".join(fields)]
+
+    result = utils.RunCmd(cmd)
+    if result.failed:
+      raise errors.CommandError("Can't get the volume information: %s - %s" %
+                                (result.fail_reason, result.output))
+
+    data = []
+    for line in result.stdout.splitlines():
+      splitted_fields = line.strip().split(sep)
+
+      if len(fields) != len(splitted_fields):
+        raise errors.CommandError("Can't parse %s output: line '%s'" %
+                                  (lvm_cmd, line))
+
+      data.append(splitted_fields)
+
+    return data
+
+  @classmethod
+  def GetPVInfo(cls, vg_names, filter_allocatable=True):
     """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
@@ -427,28 +461,53 @@ class LogicalVolume(BlockDev):
     @return: list of tuples (free_space, name) with free_space in mebibytes
 
     """
-    sep = "|"
-    command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
-               "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
-               "--separator=%s" % sep ]
-    result = utils.RunCmd(command)
-    if result.failed:
-      logging.error("Can't get the PV information: %s - %s",
-                    result.fail_reason, result.output)
+    try:
+      info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
+                                        "pv_attr"])
+    except errors.GenericError, err:
+      logging.error("Can't get PV information: %s", err)
       return None
+
     data = []
-    for line in result.stdout.splitlines():
-      fields = line.strip().split(sep)
-      if len(fields) != 4:
-        logging.error("Can't parse pvs output: line '%s'", line)
-        return None
+    for pv_name, vg_name, pv_free, pv_attr in info:
       # (possibly) skip over pvs which are not allocatable
-      if filter_allocatable and fields[3][0] != 'a':
+      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 fields[1] not in vg_names:
+      if vg_names and vg_name not in vg_names:
         continue
-      data.append((float(fields[2]), fields[0], fields[1]))
+      data.append((float(pv_free), pv_name, vg_name))
+
+    return data
+
+  @classmethod
+  def GetVGInfo(cls, vg_names, 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 filter_readonly: whether to skip over readonly VGs
+
+    @rtype: list
+    @return: list of tuples (free_space, total_size, name) with free_space in
+             MiB
+
+    """
+    try:
+      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
+                                        "vg_size"])
+    except errors.GenericError, err:
+      logging.error("Can't get VG information: %s", err)
+      return None
+
+    data = []
+    for vg_name, vg_free, vg_attr, vg_size in info:
+      # (possibly) skip over vgs which are not writable
+      if filter_readonly and vg_attr[0] == "r":
+        continue
+      # (possibly) skip over vgs which are not in the right volume group(s)
+      if vg_names and vg_name not in vg_names:
+        continue
+      data.append((float(vg_free), float(vg_size), vg_name))
 
     return data
 
@@ -642,12 +701,10 @@ class LogicalVolume(BlockDev):
     snap = LogicalVolume((self._vg_name, snap_name), None, size)
     _IgnoreError(snap.Remove)
 
-    pvs_info = self.GetPVInfo([self._vg_name])
-    if not pvs_info:
-      _ThrowError("Can't compute PV info for vg %s", self._vg_name)
-    pvs_info.sort()
-    pvs_info.reverse()
-    free_size, _, _ = pvs_info[0]
+    vg_info = self.GetVGInfo([self._vg_name])
+    if not vg_info:
+      _ThrowError("Can't compute VG info for vg %s", self._vg_name)
+    free_size, _, _ = vg_info[0]
     if free_size < size:
       _ThrowError("Not enough free space: required %s,"
                   " available %s", size, free_size)
@@ -814,7 +871,7 @@ class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
   0.7 and 8.x versions of DRBD.
 
   """
-  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
+  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
                            r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
   _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
   _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
@@ -825,6 +882,7 @@ class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
   _ST_CONNECTED = "Connected"
 
   _STATUS_FILE = "/proc/drbd"
+  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
 
   @staticmethod
   def _GetProcData(filename=_STATUS_FILE):
@@ -871,7 +929,7 @@ class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
     return results
 
   @classmethod
-  def _GetVersion(cls):
+  def _GetVersion(cls, proc_data):
     """Return the DRBD version.
 
     This will return a dict with keys:
@@ -883,7 +941,6 @@ class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
       - proto2 (only on drbd > 8.2.X)
 
     """
-    proc_data = cls._GetProcData()
     first_line = proc_data[0].strip()
     version = cls._VERSION_RE.match(first_line)
     if not version:
@@ -903,6 +960,23 @@ class BaseDRBD(BlockDev): # pylint: disable-msg=W0223
     return retval
 
   @staticmethod
+  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
+    """Returns DRBD usermode_helper currently set.
+
+    """
+    try:
+      helper = utils.ReadFile(filename).splitlines()[0]
+    except EnvironmentError, err:
+      if err.errno == errno.ENOENT:
+        _ThrowError("The file %s cannot be opened, check if the module"
+                    " is loaded (%s)", filename, str(err))
+      else:
+        _ThrowError("Can't read DRBD helper file %s: %s", filename, str(err))
+    if not helper:
+      _ThrowError("Can't read any data from %s", filename)
+    return helper
+
+  @staticmethod
   def _DevPath(minor):
     """Return the path to a drbd device for a given minor.
 
@@ -1015,7 +1089,7 @@ class DRBD8(BaseDRBD):
         children = []
     super(DRBD8, self).__init__(unique_id, children, size)
     self.major = self._DRBD_MAJOR
-    version = self._GetVersion()
+    version = self._GetVersion(self._GetProcData())
     if version['k_major'] != 8 :
       _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
                   " usage: kernel is %s.%s, ganeti wants 8.x",
@@ -1079,7 +1153,10 @@ class DRBD8(BaseDRBD):
     # pyparsing setup
     lbrace = pyp.Literal("{").suppress()
     rbrace = pyp.Literal("}").suppress()
+    lbracket = pyp.Literal("[").suppress()
+    rbracket = pyp.Literal("]").suppress()
     semi = pyp.Literal(";").suppress()
+    colon = pyp.Literal(":").suppress()
     # this also converts the value to an int
     number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
 
@@ -1092,19 +1169,19 @@ class DRBD8(BaseDRBD):
     # value types
     value = pyp.Word(pyp.alphanums + '_-/.:')
     quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
-    addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
-                 pyp.Optional(pyp.Literal("ipv6")).suppress())
-    addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
-                 pyp.Literal(':').suppress() + number)
+    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
+                 pyp.Word(pyp.nums + ".") + colon + number)
+    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
+                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
+                 pyp.Optional(rbracket) + colon + number)
     # meta device, extended syntax
-    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
-                  number + pyp.Word(']').suppress())
+    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
     # device name, extended syntax
     device_value = pyp.Literal("minor").suppress() + number
 
     # a statement
     stmt = (~rbrace + keyword + ~lbrace +
-            pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
+            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
                          device_value) +
             pyp.Optional(defa) + semi +
             pyp.Optional(pyp.restOfLine).suppress())
@@ -1238,7 +1315,7 @@ class DRBD8(BaseDRBD):
     if size:
       args.extend(["-d", "%sm" % size])
     if not constants.DRBD_BARRIERS: # disable barriers, if configured so
-      version = cls._GetVersion()
+      version = cls._GetVersion(cls._GetProcData())
       # various DRBD versions support different disk barrier options;
       # what we aim here is to revert back to the 'drain' method of
       # disk flushes and to disable metadata barriers, in effect going
@@ -1280,8 +1357,22 @@ class DRBD8(BaseDRBD):
     # about its peer.
     cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
 
+    if netutils.IP6Address.IsValid(lhost):
+      if not netutils.IP6Address.IsValid(rhost):
+        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
+                    (minor, lhost, rhost))
+      family = "ipv6"
+    elif netutils.IP4Address.IsValid(lhost):
+      if not netutils.IP4Address.IsValid(rhost):
+        _ThrowError("drbd%d: can't connect ip %s to ip %s" %
+                    (minor, lhost, rhost))
+      family = "ipv4"
+    else:
+      _ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
+
     args = ["drbdsetup", cls._DevPath(minor), "net",
-            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
+            "%s:%s:%s" % (family, lhost, lport),
+            "%s:%s:%s" % (family, rhost, rport), protocol,
             "-A", "discard-zero-changes",
             "-B", "consensus",
             "--create-device",