Revision a2cfdea2

b/INSTALL
36 36
    http://pyopenssl.sourceforge.net/
37 37
  - simplejson Python module
38 38
    http://www.undefined.org/python/#simplejson
39
  - pyparsing Python module
40
    http://pyparsing.wikispaces.com/
39 41

  
40 42
For testing, you also need the YAML module for Python (http://pyyaml.org/).
41 43

  
b/doc/install.sgml
408 408
          url="http://www.undefined.org/python/#simplejson">simplejson Python
409 409
          module</ulink></simpara>
410 410
        </listitem>
411
        <listitem>
412
          <simpara><ulink
413
          url="http://pyparsing.wikispaces.com/">pyparsing Python
414
          module</ulink></simpara>
415
        </listitem>
411 416
      </itemizedlist>
412 417

  
413 418
      <para>
b/lib/bdev.py
24 24
import re
25 25
import time
26 26
import errno
27
import pyparsing as pyp
27 28

  
28 29
from ganeti import utils
29 30
from ganeti import logger
......
1549 1550
    """
1550 1551
    return self.Shutdown()
1551 1552

  
1553
class DRBD8(BaseDRBD):
1554
  """DRBD v8.x block device.
1555

  
1556
  This implements the local host part of the DRBD device, i.e. it
1557
  doesn't do anything to the supposed peer. If you need a fully
1558
  connected DRBD pair, you need to use this class on both hosts.
1559

  
1560
  The unique_id for the drbd device is the (local_ip, local_port,
1561
  remote_ip, remote_port) tuple, and it must have two children: the
1562
  data device and the meta_device. The meta device is checked for
1563
  valid size and is zeroed on create.
1564

  
1565
  """
1566
  _DRBD_MAJOR = 147
1567
  _MAX_MINORS = 255
1568
  _PARSE_SHOW = None
1569

  
1570
  def __init__(self, unique_id, children):
1571
    super(DRBD8, self).__init__(unique_id, children)
1572
    self.major = self._DRBD_MAJOR
1573
    [kmaj, kmin, kfix, api, proto] = self._GetVersion()
1574
    if kmaj != 8:
1575
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
1576
                                    " requested ganeti usage: kernel is"
1577
                                    " %s.%s, ganeti wants 8.x" % (kmaj, kmin))
1578

  
1579
    if len(children) != 2:
1580
      raise ValueError("Invalid configuration data %s" % str(children))
1581
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
1582
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1583
    self._lhost, self._lport, self._rhost, self._rport = unique_id
1584
    self.Attach()
1585

  
1586
  @classmethod
1587
  def _InitMeta(cls, minor, dev_path):
1588
    """Initialize a meta device.
1589

  
1590
    This will not work if the given minor is in use.
1591

  
1592
    """
1593
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
1594
                           "v08", dev_path, "0", "create-md"])
1595
    if result.failed:
1596
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
1597
                                    result.output)
1598

  
1599
  @classmethod
1600
  def _FindUnusedMinor(cls):
1601
    """Find an unused DRBD device.
1602

  
1603
    This is specific to 8.x as the minors are allocated dynamically,
1604
    so non-existing numbers up to a max minor count are actually free.
1605

  
1606
    """
1607
    data = cls._GetProcData()
1608

  
1609
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
1610
    used_line = re.compile("^ *([0-9]+): cs:")
1611
    highest = None
1612
    for line in data:
1613
      match = unused_line.match(line)
1614
      if match:
1615
        return int(match.group(1))
1616
      match = used_line.match(line)
1617
      if match:
1618
        minor = int(match.group(1))
1619
        highest = max(highest, minor)
1620
    if highest is None: # there are no minors in use at all
1621
      return 0
1622
    if highest >= cls._MAX_MINORS:
1623
      logger.Error("Error: no free drbd minors!")
1624
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
1625
    return highest + 1
1626

  
1627
  @classmethod
1628
  def _IsValidMeta(cls, meta_device):
1629
    """Check if the given meta device looks like a valid one.
1630

  
1631
    """
1632
    minor = cls._FindUnusedMinor()
1633
    minor_path = cls._DevPath(minor)
1634
    result = utils.RunCmd(["drbdmeta", minor_path,
1635
                           "v08", meta_device, "0",
1636
                           "dstate"])
1637
    if result.failed:
1638
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
1639
      return False
1640
    return True
1641

  
1642
  @classmethod
1643
  def _GetShowParser(cls):
1644
    """Return a parser for `drbd show` output.
1645

  
1646
    This will either create or return an already-create parser for the
1647
    output of the command `drbd show`.
1648

  
1649
    """
1650
    if cls._PARSE_SHOW is not None:
1651
      return cls._PARSE_SHOW
1652

  
1653
    # pyparsing setup
1654
    lbrace = pyp.Literal("{").suppress()
1655
    rbrace = pyp.Literal("}").suppress()
1656
    semi = pyp.Literal(";").suppress()
1657
    # this also converts the value to an int
1658
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t:(l, [int(t[0])]))
1659

  
1660
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
1661
    defa = pyp.Literal("_is_default").suppress()
1662
    dbl_quote = pyp.Literal('"').suppress()
1663

  
1664
    keyword = pyp.Word(pyp.alphanums + '-')
1665

  
1666
    # value types
1667
    value = pyp.Word(pyp.alphanums + '_-/.:')
1668
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
1669
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
1670
                 number)
1671
    # meta device, extended syntax
1672
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
1673
                  number + pyp.Word(']').suppress())
1674

  
1675
    # a statement
1676
    stmt = (~rbrace + keyword + ~lbrace +
1677
            (addr_port ^ value ^ quoted ^ meta_value) +
1678
            pyp.Optional(defa) + semi +
1679
            pyp.Optional(pyp.restOfLine).suppress())
1680

  
1681
    # an entire section
1682
    section_name = pyp.Word(pyp.alphas + '_')
1683
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
1684

  
1685
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
1686
    bnf.ignore(comment)
1687

  
1688
    cls._PARSE_SHOW = bnf
1689

  
1690
    return bnf
1691

  
1692
  @classmethod
1693
  def _GetDevInfo(cls, minor):
1694
    """Get details about a given DRBD minor.
1695

  
1696
    This return, if available, the local backing device (as a path)
1697
    and the local and remote (ip, port) information.
1698

  
1699
    """
1700
    data = {}
1701
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
1702
    if result.failed:
1703
      logger.Error("Can't display the drbd config: %s" % result.fail_reason)
1704
      return data
1705
    out = result.stdout
1706
    if not out:
1707
      return data
1708

  
1709
    bnf = cls._GetShowParser()
1710
    # run pyparse
1711

  
1712
    try:
1713
      results = bnf.parseString(out)
1714
    except pyp.ParseException, err:
1715
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
1716
                                    str(err))
1717

  
1718
    # and massage the results into our desired format
1719
    for section in results:
1720
      sname = section[0]
1721
      if sname == "_this_host":
1722
        for lst in section[1:]:
1723
          if lst[0] == "disk":
1724
            data["local_dev"] = lst[1]
1725
          elif lst[0] == "meta-disk":
1726
            data["meta_dev"] = lst[1]
1727
            data["meta_index"] = lst[2]
1728
          elif lst[0] == "address":
1729
            data["local_addr"] = tuple(lst[1:])
1730
      elif sname == "_remote_host":
1731
        for lst in section[1:]:
1732
          if lst[0] == "address":
1733
            data["remote_addr"] = tuple(lst[1:])
1734
    return data
1735

  
1736
  def _MatchesLocal(self, info):
1737
    """Test if our local config matches with an existing device.
1738

  
1739
    The parameter should be as returned from `_GetDevInfo()`. This
1740
    method tests if our local backing device is the same as the one in
1741
    the info parameter, in effect testing if we look like the given
1742
    device.
1743

  
1744
    """
1745
    backend = self._children[0]
1746
    if backend is not None:
1747
      retval = (info["local_dev"] == backend.dev_path)
1748
    else:
1749
      retval = ("local_dev" not in info)
1750
    meta = self._children[1]
1751
    if meta is not None:
1752
      retval = retval and (info["meta_dev"] == meta.dev_path)
1753
      retval = retval and (info["meta_index"] == 0)
1754
    else:
1755
      retval = retval and ("meta_dev" not in info and
1756
                           "meta_index" not in info)
1757
    return retval
1758

  
1759
  def _MatchesNet(self, info):
1760
    """Test if our network config matches with an existing device.
1761

  
1762
    The parameter should be as returned from `_GetDevInfo()`. This
1763
    method tests if our network configuration is the same as the one
1764
    in the info parameter, in effect testing if we look like the given
1765
    device.
1766

  
1767
    """
1768
    if (((self._lhost is None and not ("local_addr" in info)) and
1769
         (self._rhost is None and not ("remote_addr" in info)))):
1770
      return True
1771

  
1772
    if self._lhost is None:
1773
      return False
1774

  
1775
    if not ("local_addr" in info and
1776
            "remote_addr" in info):
1777
      return False
1778

  
1779
    retval = (info["local_addr"] == (self._lhost, self._lport))
1780
    retval = (retval and
1781
              info["remote_addr"] == (self._rhost, self._rport))
1782
    return retval
1783

  
1784
  @classmethod
1785
  def _AssembleLocal(cls, minor, backend, meta):
1786
    """Configure the local part of a DRBD device.
1787

  
1788
    This is the first thing that must be done on an unconfigured DRBD
1789
    device. And it must be done only once.
1790

  
1791
    """
1792
    if not cls._IsValidMeta(meta):
1793
      return False
1794
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disk",
1795
                           backend, meta, "0", "-e", "detach",
1796
                           "--create-device"])
1797
    if result.failed:
1798
      logger.Error("Can't attach local disk: %s" % result.output)
1799
    return not result.failed
1800

  
1801
  @classmethod
1802
  def _AssembleNet(cls, minor, net_info, protocol,
1803
                   dual_pri=False, hmac=None, secret=None):
1804
    """Configure the network part of the device.
1805

  
1806
    """
1807
    lhost, lport, rhost, rport = net_info
1808
    args = ["drbdsetup", cls._DevPath(minor), "net",
1809
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport),
1810
            protocol]
1811
    if dual_pri:
1812
      args.append("-m")
1813
    if hmac and secret:
1814
      args.extend(["-a", hmac, "-x", secret])
1815
    result = utils.RunCmd(args)
1816
    if result.failed:
1817
      logger.Error("Can't setup network for dbrd device: %s" %
1818
                   result.fail_reason)
1819
      return False
1820

  
1821
    timeout = time.time() + 10
1822
    ok = False
1823
    while time.time() < timeout:
1824
      info = cls._GetDevInfo(minor)
1825
      if not "local_addr" in info or not "remote_addr" in info:
1826
        time.sleep(1)
1827
        continue
1828
      if (info["local_addr"] != (lhost, lport) or
1829
          info["remote_addr"] != (rhost, rport)):
1830
        time.sleep(1)
1831
        continue
1832
      ok = True
1833
      break
1834
    if not ok:
1835
      logger.Error("Timeout while configuring network")
1836
      return False
1837
    return True
1838

  
1839
  def SetSyncSpeed(self, kbytes):
1840
    """Set the speed of the DRBD syncer.
1841

  
1842
    """
1843
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1844
    if self.minor is None:
1845
      logger.Info("Instance not attached to a device")
1846
      return False
1847
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1848
                           kbytes])
1849
    if result.failed:
1850
      logger.Error("Can't change syncer rate: %s " % result.fail_reason)
1851
    return not result.failed and children_result
1852

  
1853
  def GetSyncStatus(self):
1854
    """Returns the sync status of the device.
1855

  
1856
    Returns:
1857
     (sync_percent, estimated_time)
1858

  
1859
    If sync_percent is None, it means all is ok
1860
    If estimated_time is None, it means we can't esimate
1861
    the time needed, otherwise it's the time left in seconds
1862

  
1863
    """
1864
    if self.minor is None and not self.Attach():
1865
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1866
    proc_info = self._MassageProcData(self._GetProcData())
1867
    if self.minor not in proc_info:
1868
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1869
                                    self.minor)
1870
    line = proc_info[self.minor]
1871
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1872
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1873
    if match:
1874
      sync_percent = float(match.group(1))
1875
      hours = int(match.group(2))
1876
      minutes = int(match.group(3))
1877
      seconds = int(match.group(4))
1878
      est_time = hours * 3600 + minutes * 60 + seconds
1879
    else:
1880
      sync_percent = None
1881
      est_time = None
1882
    match = re.match("^ *[0-9]+: cs:([^ ]+).*$", line)
1883
    if not match:
1884
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1885
                                    self.minor)
1886
    client_state = match.group(1)
1887
    is_degraded = client_state != "Connected"
1888
    return sync_percent, est_time, is_degraded
1889

  
1890
  def GetStatus(self):
1891
    """Compute the status of the DRBD device
1892

  
1893
    Note that DRBD devices don't have the STATUS_EXISTING state.
1894

  
1895
    """
1896
    if self.minor is None and not self.Attach():
1897
      return self.STATUS_UNKNOWN
1898

  
1899
    data = self._GetProcData()
1900
    match = re.compile("^ *%d: cs:[^ ]+ st:(Primary|Secondary)/.*$" %
1901
                       self.minor)
1902
    for line in data:
1903
      mresult = match.match(line)
1904
      if mresult:
1905
        break
1906
    else:
1907
      logger.Error("Can't find myself!")
1908
      return self.STATUS_UNKNOWN
1909

  
1910
    state = mresult.group(2)
1911
    if state == "Primary":
1912
      result = self.STATUS_ONLINE
1913
    else:
1914
      result = self.STATUS_STANDBY
1915

  
1916
    return result
1917

  
1918
  def Open(self, force=False):
1919
    """Make the local state primary.
1920

  
1921
    If the 'force' parameter is given, the '--do-what-I-say' parameter
1922
    is given. Since this is a pottentialy dangerous operation, the
1923
    force flag should be only given after creation, when it actually
1924
    has to be given.
1925

  
1926
    """
1927
    if self.minor is None and not self.Attach():
1928
      logger.Error("DRBD cannot attach to a device during open")
1929
      return False
1930
    cmd = ["drbdsetup", self.dev_path, "primary"]
1931
    if force:
1932
      cmd.append("-o")
1933
    result = utils.RunCmd(cmd)
1934
    if result.failed:
1935
      logger.Error("Can't make drbd device primary: %s" % result.output)
1936
      return False
1937
    return True
1938

  
1939
  def Close(self):
1940
    """Make the local state secondary.
1941

  
1942
    This will, of course, fail if the device is in use.
1943

  
1944
    """
1945
    if self.minor is None and not self.Attach():
1946
      logger.Info("Instance not attached to a device")
1947
      raise errors.BlockDeviceError("Can't find device")
1948
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1949
    if result.failed:
1950
      logger.Error("Can't switch drbd device to secondary: %s" % result.output)
1951
      raise errors.BlockDeviceError("Can't switch drbd device to secondary")
1952

  
1953
  def Attach(self):
1954
    """Find a DRBD device which matches our config and attach to it.
1955

  
1956
    In case of partially attached (local device matches but no network
1957
    setup), we perform the network attach. If successful, we re-test
1958
    the attach if can return success.
1959

  
1960
    """
1961
    for minor in self._GetUsedDevs():
1962
      info = self._GetDevInfo(minor)
1963
      match_l = self._MatchesLocal(info)
1964
      match_r = self._MatchesNet(info)
1965
      if match_l and match_r:
1966
        break
1967
      if match_l and not match_r and "local_addr" not in info:
1968
        res_r = self._AssembleNet(minor,
1969
                                  (self._lhost, self._lport,
1970
                                   self._rhost, self._rport),
1971
                                  "C")
1972
        if res_r and self._MatchesNet(self._GetDevInfo(minor)):
1973
          break
1974
    else:
1975
      minor = None
1976

  
1977
    self._SetFromMinor(minor)
1978
    return minor is not None
1979

  
1980
  def Assemble(self):
1981
    """Assemble the drbd.
1982

  
1983
    Method:
1984
      - if we have a local backing device, we bind to it by:
1985
        - checking the list of used drbd devices
1986
        - check if the local minor use of any of them is our own device
1987
        - if yes, abort?
1988
        - if not, bind
1989
      - if we have a local/remote net info:
1990
        - redo the local backing device step for the remote device
1991
        - check if any drbd device is using the local port,
1992
          if yes abort
1993
        - check if any remote drbd device is using the remote
1994
          port, if yes abort (for now)
1995
        - bind our net port
1996
        - bind the remote net port
1997

  
1998
    """
1999
    self.Attach()
2000
    if self.minor is not None:
2001
      logger.Info("Already assembled")
2002
      return True
2003

  
2004
    result = super(DRBD8, self).Assemble()
2005
    if not result:
2006
      return result
2007

  
2008
    minor = self._FindUnusedMinor()
2009
    need_localdev_teardown = False
2010
    if self._children[0]:
2011
      result = self._AssembleLocal(minor, self._children[0].dev_path,
2012
                                   self._children[1].dev_path)
2013
      if not result:
2014
        return False
2015
      need_localdev_teardown = True
2016
    if self._lhost and self._lport and self._rhost and self._rport:
2017
      result = self._AssembleNet(minor,
2018
                                 (self._lhost, self._lport,
2019
                                  self._rhost, self._rport),
2020
                                 "C")
2021
      if not result:
2022
        if need_localdev_teardown:
2023
          # we will ignore failures from this
2024
          logger.Error("net setup failed, tearing down local device")
2025
          self._ShutdownAll(minor)
2026
        return False
2027
    self._SetFromMinor(minor)
2028
    return True
2029

  
2030
  @classmethod
2031
  def _ShutdownAll(cls, minor):
2032
    """Deactivate the device.
2033

  
2034
    This will, of course, fail if the device is in use.
2035

  
2036
    """
2037
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
2038
    if result.failed:
2039
      logger.Error("Can't shutdown drbd device: %s" % result.output)
2040
    return not result.failed
2041

  
2042
  def Shutdown(self):
2043
    """Shutdown the DRBD device.
2044

  
2045
    """
2046
    if self.minor is None and not self.Attach():
2047
      logger.Info("DRBD device not attached to a device during Shutdown")
2048
      return True
2049
    if not self._ShutdownAll(self.minor):
2050
      return False
2051
    self.minor = None
2052
    self.dev_path = None
2053
    return True
2054

  
2055
  def Remove(self):
2056
    """Stub remove for DRBD devices.
2057

  
2058
    """
2059
    return self.Shutdown()
2060

  
2061
  @classmethod
2062
  def Create(cls, unique_id, children, size):
2063
    """Create a new DRBD8 device.
2064

  
2065
    Since DRBD devices are not created per se, just assembled, this
2066
    function only initializes the metadata.
2067

  
2068
    """
2069
    if len(children) != 2:
2070
      raise errors.ProgrammerError("Invalid setup for the drbd device")
2071
    meta = children[1]
2072
    meta.Assemble()
2073
    if not meta.Attach():
2074
      raise errors.BlockDeviceError("Can't attach to meta device")
2075
    if not cls._CheckMetaSize(meta.dev_path):
2076
      raise errors.BlockDeviceError("Invalid meta device size")
2077
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
2078
    if not cls._IsValidMeta(meta.dev_path):
2079
      raise errors.BlockDeviceError("Cannot initalize meta device")
2080
    return cls(unique_id, children)
2081

  
1552 2082

  
1553 2083
DEV_MAP = {
1554 2084
  constants.LD_LV: LogicalVolume,

Also available in: Unified diff