Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd_info.py @ ac156ecd

History | View | Annotate | Download (14.5 kB)

1 27d69b25 Thomas Thrainer
#
2 27d69b25 Thomas Thrainer
#
3 27d69b25 Thomas Thrainer
4 27d69b25 Thomas Thrainer
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
5 27d69b25 Thomas Thrainer
#
6 27d69b25 Thomas Thrainer
# This program is free software; you can redistribute it and/or modify
7 27d69b25 Thomas Thrainer
# it under the terms of the GNU General Public License as published by
8 27d69b25 Thomas Thrainer
# the Free Software Foundation; either version 2 of the License, or
9 27d69b25 Thomas Thrainer
# (at your option) any later version.
10 27d69b25 Thomas Thrainer
#
11 27d69b25 Thomas Thrainer
# This program is distributed in the hope that it will be useful, but
12 27d69b25 Thomas Thrainer
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 27d69b25 Thomas Thrainer
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 27d69b25 Thomas Thrainer
# General Public License for more details.
15 27d69b25 Thomas Thrainer
#
16 27d69b25 Thomas Thrainer
# You should have received a copy of the GNU General Public License
17 27d69b25 Thomas Thrainer
# along with this program; if not, write to the Free Software
18 27d69b25 Thomas Thrainer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 27d69b25 Thomas Thrainer
# 02110-1301, USA.
20 27d69b25 Thomas Thrainer
21 27d69b25 Thomas Thrainer
22 27d69b25 Thomas Thrainer
"""DRBD information parsing utilities"""
23 27d69b25 Thomas Thrainer
24 27d69b25 Thomas Thrainer
import errno
25 27d69b25 Thomas Thrainer
import pyparsing as pyp
26 27d69b25 Thomas Thrainer
import re
27 27d69b25 Thomas Thrainer
28 27d69b25 Thomas Thrainer
from ganeti import constants
29 27d69b25 Thomas Thrainer
from ganeti import utils
30 27d69b25 Thomas Thrainer
from ganeti import errors
31 27d69b25 Thomas Thrainer
from ganeti import compat
32 cde49218 Helga Velroyen
from ganeti.storage import base
33 27d69b25 Thomas Thrainer
34 27d69b25 Thomas Thrainer
35 27d69b25 Thomas Thrainer
class DRBD8Status(object): # pylint: disable=R0902
36 27d69b25 Thomas Thrainer
  """A DRBD status representation class.
37 27d69b25 Thomas Thrainer

38 27d69b25 Thomas Thrainer
  Note that this class is meant to be used to parse one of the entries returned
39 27d69b25 Thomas Thrainer
  from L{DRBD8Info._JoinLinesPerMinor}.
40 27d69b25 Thomas Thrainer

41 27d69b25 Thomas Thrainer
  """
42 27d69b25 Thomas Thrainer
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
43 27d69b25 Thomas Thrainer
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
44 78f99abb Michele Tartara
                       r"\s+ds:([^/]+)/(\S+)\s+.*$")
45 27d69b25 Thomas Thrainer
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
46 27d69b25 Thomas Thrainer
                       # Due to a bug in drbd in the kernel, introduced in
47 27d69b25 Thomas Thrainer
                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
48 78f99abb Michele Tartara
                       r"(?:\s|M)"
49 78f99abb Michele Tartara
                       r"finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
50 27d69b25 Thomas Thrainer
51 27d69b25 Thomas Thrainer
  CS_UNCONFIGURED = "Unconfigured"
52 27d69b25 Thomas Thrainer
  CS_STANDALONE = "StandAlone"
53 27d69b25 Thomas Thrainer
  CS_WFCONNECTION = "WFConnection"
54 27d69b25 Thomas Thrainer
  CS_WFREPORTPARAMS = "WFReportParams"
55 27d69b25 Thomas Thrainer
  CS_CONNECTED = "Connected"
56 27d69b25 Thomas Thrainer
  CS_STARTINGSYNCS = "StartingSyncS"
57 27d69b25 Thomas Thrainer
  CS_STARTINGSYNCT = "StartingSyncT"
58 27d69b25 Thomas Thrainer
  CS_WFBITMAPS = "WFBitMapS"
59 27d69b25 Thomas Thrainer
  CS_WFBITMAPT = "WFBitMapT"
60 27d69b25 Thomas Thrainer
  CS_WFSYNCUUID = "WFSyncUUID"
61 27d69b25 Thomas Thrainer
  CS_SYNCSOURCE = "SyncSource"
62 27d69b25 Thomas Thrainer
  CS_SYNCTARGET = "SyncTarget"
63 27d69b25 Thomas Thrainer
  CS_PAUSEDSYNCS = "PausedSyncS"
64 27d69b25 Thomas Thrainer
  CS_PAUSEDSYNCT = "PausedSyncT"
65 27d69b25 Thomas Thrainer
  CSET_SYNC = compat.UniqueFrozenset([
66 27d69b25 Thomas Thrainer
    CS_WFREPORTPARAMS,
67 27d69b25 Thomas Thrainer
    CS_STARTINGSYNCS,
68 27d69b25 Thomas Thrainer
    CS_STARTINGSYNCT,
69 27d69b25 Thomas Thrainer
    CS_WFBITMAPS,
70 27d69b25 Thomas Thrainer
    CS_WFBITMAPT,
71 27d69b25 Thomas Thrainer
    CS_WFSYNCUUID,
72 27d69b25 Thomas Thrainer
    CS_SYNCSOURCE,
73 27d69b25 Thomas Thrainer
    CS_SYNCTARGET,
74 27d69b25 Thomas Thrainer
    CS_PAUSEDSYNCS,
75 27d69b25 Thomas Thrainer
    CS_PAUSEDSYNCT,
76 27d69b25 Thomas Thrainer
    ])
77 27d69b25 Thomas Thrainer
78 27d69b25 Thomas Thrainer
  DS_DISKLESS = "Diskless"
79 27d69b25 Thomas Thrainer
  DS_ATTACHING = "Attaching" # transient state
80 27d69b25 Thomas Thrainer
  DS_FAILED = "Failed" # transient state, next: diskless
81 27d69b25 Thomas Thrainer
  DS_NEGOTIATING = "Negotiating" # transient state
82 27d69b25 Thomas Thrainer
  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
83 27d69b25 Thomas Thrainer
  DS_OUTDATED = "Outdated"
84 27d69b25 Thomas Thrainer
  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
85 27d69b25 Thomas Thrainer
  DS_CONSISTENT = "Consistent"
86 27d69b25 Thomas Thrainer
  DS_UPTODATE = "UpToDate" # normal state
87 27d69b25 Thomas Thrainer
88 27d69b25 Thomas Thrainer
  RO_PRIMARY = "Primary"
89 27d69b25 Thomas Thrainer
  RO_SECONDARY = "Secondary"
90 27d69b25 Thomas Thrainer
  RO_UNKNOWN = "Unknown"
91 27d69b25 Thomas Thrainer
92 27d69b25 Thomas Thrainer
  def __init__(self, procline):
93 27d69b25 Thomas Thrainer
    u = self.UNCONF_RE.match(procline)
94 27d69b25 Thomas Thrainer
    if u:
95 27d69b25 Thomas Thrainer
      self.cstatus = self.CS_UNCONFIGURED
96 27d69b25 Thomas Thrainer
      self.lrole = self.rrole = self.ldisk = self.rdisk = None
97 27d69b25 Thomas Thrainer
    else:
98 27d69b25 Thomas Thrainer
      m = self.LINE_RE.match(procline)
99 27d69b25 Thomas Thrainer
      if not m:
100 27d69b25 Thomas Thrainer
        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
101 27d69b25 Thomas Thrainer
      self.cstatus = m.group(1)
102 27d69b25 Thomas Thrainer
      self.lrole = m.group(2)
103 27d69b25 Thomas Thrainer
      self.rrole = m.group(3)
104 27d69b25 Thomas Thrainer
      self.ldisk = m.group(4)
105 27d69b25 Thomas Thrainer
      self.rdisk = m.group(5)
106 27d69b25 Thomas Thrainer
107 27d69b25 Thomas Thrainer
    # end reading of data from the LINE_RE or UNCONF_RE
108 27d69b25 Thomas Thrainer
109 27d69b25 Thomas Thrainer
    self.is_standalone = self.cstatus == self.CS_STANDALONE
110 27d69b25 Thomas Thrainer
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
111 27d69b25 Thomas Thrainer
    self.is_connected = self.cstatus == self.CS_CONNECTED
112 27d69b25 Thomas Thrainer
    self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED
113 27d69b25 Thomas Thrainer
    self.is_primary = self.lrole == self.RO_PRIMARY
114 27d69b25 Thomas Thrainer
    self.is_secondary = self.lrole == self.RO_SECONDARY
115 27d69b25 Thomas Thrainer
    self.peer_primary = self.rrole == self.RO_PRIMARY
116 27d69b25 Thomas Thrainer
    self.peer_secondary = self.rrole == self.RO_SECONDARY
117 27d69b25 Thomas Thrainer
    self.both_primary = self.is_primary and self.peer_primary
118 27d69b25 Thomas Thrainer
    self.both_secondary = self.is_secondary and self.peer_secondary
119 27d69b25 Thomas Thrainer
120 27d69b25 Thomas Thrainer
    self.is_diskless = self.ldisk == self.DS_DISKLESS
121 27d69b25 Thomas Thrainer
    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
122 64e92328 Klaus Aehlig
    self.peer_disk_uptodate = self.rdisk == self.DS_UPTODATE
123 27d69b25 Thomas Thrainer
124 27d69b25 Thomas Thrainer
    self.is_in_resync = self.cstatus in self.CSET_SYNC
125 27d69b25 Thomas Thrainer
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
126 27d69b25 Thomas Thrainer
127 27d69b25 Thomas Thrainer
    m = self.SYNC_RE.match(procline)
128 27d69b25 Thomas Thrainer
    if m:
129 27d69b25 Thomas Thrainer
      self.sync_percent = float(m.group(1))
130 27d69b25 Thomas Thrainer
      hours = int(m.group(2))
131 27d69b25 Thomas Thrainer
      minutes = int(m.group(3))
132 27d69b25 Thomas Thrainer
      seconds = int(m.group(4))
133 27d69b25 Thomas Thrainer
      self.est_time = hours * 3600 + minutes * 60 + seconds
134 27d69b25 Thomas Thrainer
    else:
135 27d69b25 Thomas Thrainer
      # we have (in this if branch) no percent information, but if
136 27d69b25 Thomas Thrainer
      # we're resyncing we need to 'fake' a sync percent information,
137 27d69b25 Thomas Thrainer
      # as this is how cmdlib determines if it makes sense to wait for
138 27d69b25 Thomas Thrainer
      # resyncing or not
139 27d69b25 Thomas Thrainer
      if self.is_in_resync:
140 27d69b25 Thomas Thrainer
        self.sync_percent = 0
141 27d69b25 Thomas Thrainer
      else:
142 27d69b25 Thomas Thrainer
        self.sync_percent = None
143 27d69b25 Thomas Thrainer
      self.est_time = None
144 27d69b25 Thomas Thrainer
145 5ee24e45 Helga Velroyen
  def __repr__(self):
146 5ee24e45 Helga Velroyen
    return ("<%s: cstatus=%s, lrole=%s, rrole=%s, ldisk=%s, rdisk=%s>" %
147 5ee24e45 Helga Velroyen
            (self.__class__, self.cstatus, self.lrole, self.rrole,
148 5ee24e45 Helga Velroyen
             self.ldisk, self.rdisk))
149 5ee24e45 Helga Velroyen
150 27d69b25 Thomas Thrainer
151 27d69b25 Thomas Thrainer
class DRBD8Info(object):
152 27d69b25 Thomas Thrainer
  """Represents information DRBD exports (usually via /proc/drbd).
153 27d69b25 Thomas Thrainer

154 27d69b25 Thomas Thrainer
  An instance of this class is created by one of the CreateFrom... methods.
155 27d69b25 Thomas Thrainer

156 27d69b25 Thomas Thrainer
  """
157 27d69b25 Thomas Thrainer
158 5bb0a1cb Thomas Thrainer
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.(\d+))?"
159 27d69b25 Thomas Thrainer
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
160 27d69b25 Thomas Thrainer
  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
161 27d69b25 Thomas Thrainer
162 27d69b25 Thomas Thrainer
  def __init__(self, lines):
163 27d69b25 Thomas Thrainer
    self._version = self._ParseVersion(lines)
164 27d69b25 Thomas Thrainer
    self._minors, self._line_per_minor = self._JoinLinesPerMinor(lines)
165 27d69b25 Thomas Thrainer
166 27d69b25 Thomas Thrainer
  def GetVersion(self):
167 27d69b25 Thomas Thrainer
    """Return the DRBD version.
168 27d69b25 Thomas Thrainer

169 27d69b25 Thomas Thrainer
    This will return a dict with keys:
170 27d69b25 Thomas Thrainer
      - k_major
171 27d69b25 Thomas Thrainer
      - k_minor
172 27d69b25 Thomas Thrainer
      - k_point
173 5bb0a1cb Thomas Thrainer
      - k_fix (only on some drbd versions)
174 27d69b25 Thomas Thrainer
      - api
175 27d69b25 Thomas Thrainer
      - proto
176 27d69b25 Thomas Thrainer
      - proto2 (only on drbd > 8.2.X)
177 27d69b25 Thomas Thrainer

178 27d69b25 Thomas Thrainer
    """
179 27d69b25 Thomas Thrainer
    return self._version
180 27d69b25 Thomas Thrainer
181 5bb0a1cb Thomas Thrainer
  def GetVersionString(self):
182 5bb0a1cb Thomas Thrainer
    """Return the DRBD version as a single string.
183 5bb0a1cb Thomas Thrainer

184 5bb0a1cb Thomas Thrainer
    """
185 5bb0a1cb Thomas Thrainer
    version = self.GetVersion()
186 5bb0a1cb Thomas Thrainer
    retval = "%d.%d.%d" % \
187 5bb0a1cb Thomas Thrainer
             (version["k_major"], version["k_minor"], version["k_point"])
188 5bb0a1cb Thomas Thrainer
    if "k_fix" in version:
189 5bb0a1cb Thomas Thrainer
      retval += ".%s" % version["k_fix"]
190 5bb0a1cb Thomas Thrainer
191 5bb0a1cb Thomas Thrainer
    retval += " (api:%d/proto:%d" % (version["api"], version["proto"])
192 5bb0a1cb Thomas Thrainer
    if "proto2" in version:
193 5bb0a1cb Thomas Thrainer
      retval += "-%s" % version["proto2"]
194 5bb0a1cb Thomas Thrainer
    retval += ")"
195 5bb0a1cb Thomas Thrainer
    return retval
196 5bb0a1cb Thomas Thrainer
197 27d69b25 Thomas Thrainer
  def GetMinors(self):
198 27d69b25 Thomas Thrainer
    """Return a list of minor for which information is available.
199 27d69b25 Thomas Thrainer

200 27d69b25 Thomas Thrainer
    This list is ordered in exactly the order which was found in the underlying
201 27d69b25 Thomas Thrainer
    data.
202 27d69b25 Thomas Thrainer

203 27d69b25 Thomas Thrainer
    """
204 27d69b25 Thomas Thrainer
    return self._minors
205 27d69b25 Thomas Thrainer
206 27d69b25 Thomas Thrainer
  def HasMinorStatus(self, minor):
207 27d69b25 Thomas Thrainer
    return minor in self._line_per_minor
208 27d69b25 Thomas Thrainer
209 27d69b25 Thomas Thrainer
  def GetMinorStatus(self, minor):
210 27d69b25 Thomas Thrainer
    return DRBD8Status(self._line_per_minor[minor])
211 27d69b25 Thomas Thrainer
212 27d69b25 Thomas Thrainer
  def _ParseVersion(self, lines):
213 27d69b25 Thomas Thrainer
    first_line = lines[0].strip()
214 27d69b25 Thomas Thrainer
    version = self._VERSION_RE.match(first_line)
215 27d69b25 Thomas Thrainer
    if not version:
216 27d69b25 Thomas Thrainer
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
217 27d69b25 Thomas Thrainer
                                    first_line)
218 27d69b25 Thomas Thrainer
219 27d69b25 Thomas Thrainer
    values = version.groups()
220 27d69b25 Thomas Thrainer
    retval = {
221 27d69b25 Thomas Thrainer
      "k_major": int(values[0]),
222 27d69b25 Thomas Thrainer
      "k_minor": int(values[1]),
223 27d69b25 Thomas Thrainer
      "k_point": int(values[2]),
224 5bb0a1cb Thomas Thrainer
      "api": int(values[4]),
225 5bb0a1cb Thomas Thrainer
      "proto": int(values[5]),
226 27d69b25 Thomas Thrainer
      }
227 5bb0a1cb Thomas Thrainer
    if values[3] is not None:
228 5bb0a1cb Thomas Thrainer
      retval["k_fix"] = values[3]
229 5bb0a1cb Thomas Thrainer
    if values[6] is not None:
230 5bb0a1cb Thomas Thrainer
      retval["proto2"] = values[6]
231 27d69b25 Thomas Thrainer
232 27d69b25 Thomas Thrainer
    return retval
233 27d69b25 Thomas Thrainer
234 27d69b25 Thomas Thrainer
  def _JoinLinesPerMinor(self, lines):
235 27d69b25 Thomas Thrainer
    """Transform the raw lines into a dictionary based on the minor.
236 27d69b25 Thomas Thrainer

237 27d69b25 Thomas Thrainer
    @return: a dictionary of minor: joined lines from /proc/drbd
238 27d69b25 Thomas Thrainer
        for that minor
239 27d69b25 Thomas Thrainer

240 27d69b25 Thomas Thrainer
    """
241 27d69b25 Thomas Thrainer
    minors = []
242 27d69b25 Thomas Thrainer
    results = {}
243 27d69b25 Thomas Thrainer
    old_minor = old_line = None
244 27d69b25 Thomas Thrainer
    for line in lines:
245 27d69b25 Thomas Thrainer
      if not line: # completely empty lines, as can be returned by drbd8.0+
246 27d69b25 Thomas Thrainer
        continue
247 27d69b25 Thomas Thrainer
      lresult = self._VALID_LINE_RE.match(line)
248 27d69b25 Thomas Thrainer
      if lresult is not None:
249 27d69b25 Thomas Thrainer
        if old_minor is not None:
250 27d69b25 Thomas Thrainer
          minors.append(old_minor)
251 27d69b25 Thomas Thrainer
          results[old_minor] = old_line
252 27d69b25 Thomas Thrainer
        old_minor = int(lresult.group(1))
253 27d69b25 Thomas Thrainer
        old_line = line
254 27d69b25 Thomas Thrainer
      else:
255 27d69b25 Thomas Thrainer
        if old_minor is not None:
256 27d69b25 Thomas Thrainer
          old_line += " " + line.strip()
257 27d69b25 Thomas Thrainer
    # add last line
258 27d69b25 Thomas Thrainer
    if old_minor is not None:
259 27d69b25 Thomas Thrainer
      minors.append(old_minor)
260 27d69b25 Thomas Thrainer
      results[old_minor] = old_line
261 27d69b25 Thomas Thrainer
    return minors, results
262 27d69b25 Thomas Thrainer
263 27d69b25 Thomas Thrainer
  @staticmethod
264 27d69b25 Thomas Thrainer
  def CreateFromLines(lines):
265 27d69b25 Thomas Thrainer
    return DRBD8Info(lines)
266 27d69b25 Thomas Thrainer
267 27d69b25 Thomas Thrainer
  @staticmethod
268 27d69b25 Thomas Thrainer
  def CreateFromFile(filename=constants.DRBD_STATUS_FILE):
269 27d69b25 Thomas Thrainer
    try:
270 27d69b25 Thomas Thrainer
      lines = utils.ReadFile(filename).splitlines()
271 27d69b25 Thomas Thrainer
    except EnvironmentError, err:
272 27d69b25 Thomas Thrainer
      if err.errno == errno.ENOENT:
273 27d69b25 Thomas Thrainer
        base.ThrowError("The file %s cannot be opened, check if the module"
274 27d69b25 Thomas Thrainer
                        " is loaded (%s)", filename, str(err))
275 27d69b25 Thomas Thrainer
      else:
276 27d69b25 Thomas Thrainer
        base.ThrowError("Can't read the DRBD proc file %s: %s",
277 27d69b25 Thomas Thrainer
                        filename, str(err))
278 27d69b25 Thomas Thrainer
    if not lines:
279 27d69b25 Thomas Thrainer
      base.ThrowError("Can't read any data from %s", filename)
280 27d69b25 Thomas Thrainer
    return DRBD8Info.CreateFromLines(lines)
281 27d69b25 Thomas Thrainer
282 27d69b25 Thomas Thrainer
283 5520d04d Thomas Thrainer
class BaseShowInfo(object):
284 5520d04d Thomas Thrainer
  """Base class for parsing the `drbdsetup show` output.
285 5520d04d Thomas Thrainer

286 5520d04d Thomas Thrainer
  Holds various common pyparsing expressions which are used by subclasses. Also
287 5520d04d Thomas Thrainer
  provides caching of the constructed parser.
288 27d69b25 Thomas Thrainer

289 27d69b25 Thomas Thrainer
  """
290 27d69b25 Thomas Thrainer
  _PARSE_SHOW = None
291 27d69b25 Thomas Thrainer
292 5520d04d Thomas Thrainer
  # pyparsing setup
293 5520d04d Thomas Thrainer
  _lbrace = pyp.Literal("{").suppress()
294 5520d04d Thomas Thrainer
  _rbrace = pyp.Literal("}").suppress()
295 5520d04d Thomas Thrainer
  _lbracket = pyp.Literal("[").suppress()
296 5520d04d Thomas Thrainer
  _rbracket = pyp.Literal("]").suppress()
297 5520d04d Thomas Thrainer
  _semi = pyp.Literal(";").suppress()
298 5520d04d Thomas Thrainer
  _colon = pyp.Literal(":").suppress()
299 5520d04d Thomas Thrainer
  # this also converts the value to an int
300 5520d04d Thomas Thrainer
  _number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
301 5520d04d Thomas Thrainer
302 5520d04d Thomas Thrainer
  _comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
303 5520d04d Thomas Thrainer
  _defa = pyp.Literal("_is_default").suppress()
304 5520d04d Thomas Thrainer
  _dbl_quote = pyp.Literal('"').suppress()
305 5520d04d Thomas Thrainer
306 5520d04d Thomas Thrainer
  _keyword = pyp.Word(pyp.alphanums + "-")
307 5520d04d Thomas Thrainer
308 5520d04d Thomas Thrainer
  # value types
309 5520d04d Thomas Thrainer
  _value = pyp.Word(pyp.alphanums + "_-/.:")
310 5520d04d Thomas Thrainer
  _quoted = _dbl_quote + pyp.CharsNotIn('"') + _dbl_quote
311 5520d04d Thomas Thrainer
  _ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
312 5520d04d Thomas Thrainer
                pyp.Word(pyp.nums + ".") + _colon + _number)
313 5520d04d Thomas Thrainer
  _ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
314 5520d04d Thomas Thrainer
                pyp.Optional(_lbracket) + pyp.Word(pyp.hexnums + ":") +
315 5520d04d Thomas Thrainer
                pyp.Optional(_rbracket) + _colon + _number)
316 5520d04d Thomas Thrainer
  # meta device, extended syntax
317 5520d04d Thomas Thrainer
  _meta_value = ((_value ^ _quoted) + _lbracket + _number + _rbracket)
318 5520d04d Thomas Thrainer
  # device name, extended syntax
319 5520d04d Thomas Thrainer
  _device_value = pyp.Literal("minor").suppress() + _number
320 5520d04d Thomas Thrainer
321 5520d04d Thomas Thrainer
  # a statement
322 5520d04d Thomas Thrainer
  _stmt = (~_rbrace + _keyword + ~_lbrace +
323 5520d04d Thomas Thrainer
           pyp.Optional(_ipv4_addr ^ _ipv6_addr ^ _value ^ _quoted ^
324 5520d04d Thomas Thrainer
                        _meta_value ^ _device_value) +
325 5520d04d Thomas Thrainer
           pyp.Optional(_defa) + _semi +
326 5520d04d Thomas Thrainer
           pyp.Optional(pyp.restOfLine).suppress())
327 27d69b25 Thomas Thrainer
328 27d69b25 Thomas Thrainer
  @classmethod
329 27d69b25 Thomas Thrainer
  def GetDevInfo(cls, show_data):
330 27d69b25 Thomas Thrainer
    """Parse details about a given DRBD minor.
331 27d69b25 Thomas Thrainer

332 27d69b25 Thomas Thrainer
    This returns, if available, the local backing device (as a path)
333 27d69b25 Thomas Thrainer
    and the local and remote (ip, port) information from a string
334 27d69b25 Thomas Thrainer
    containing the output of the `drbdsetup show` command as returned
335 239364d0 Thomas Thrainer
    by DRBD8Dev._GetShowData.
336 27d69b25 Thomas Thrainer

337 27d69b25 Thomas Thrainer
    This will return a dict with keys:
338 27d69b25 Thomas Thrainer
      - local_dev
339 27d69b25 Thomas Thrainer
      - meta_dev
340 27d69b25 Thomas Thrainer
      - meta_index
341 27d69b25 Thomas Thrainer
      - local_addr
342 27d69b25 Thomas Thrainer
      - remote_addr
343 27d69b25 Thomas Thrainer

344 27d69b25 Thomas Thrainer
    """
345 27d69b25 Thomas Thrainer
    if not show_data:
346 5520d04d Thomas Thrainer
      return {}
347 27d69b25 Thomas Thrainer
348 27d69b25 Thomas Thrainer
    try:
349 27d69b25 Thomas Thrainer
      # run pyparse
350 27d69b25 Thomas Thrainer
      results = (cls._GetShowParser()).parseString(show_data)
351 27d69b25 Thomas Thrainer
    except pyp.ParseException, err:
352 27d69b25 Thomas Thrainer
      base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
353 27d69b25 Thomas Thrainer
354 5520d04d Thomas Thrainer
    return cls._TransformParseResult(results)
355 5520d04d Thomas Thrainer
356 5520d04d Thomas Thrainer
  @classmethod
357 5520d04d Thomas Thrainer
  def _TransformParseResult(cls, parse_result):
358 5520d04d Thomas Thrainer
    raise NotImplementedError
359 5520d04d Thomas Thrainer
360 5520d04d Thomas Thrainer
  @classmethod
361 5520d04d Thomas Thrainer
  def _GetShowParser(cls):
362 5520d04d Thomas Thrainer
    """Return a parser for `drbd show` output.
363 5520d04d Thomas Thrainer

364 5520d04d Thomas Thrainer
    This will either create or return an already-created parser for the
365 5520d04d Thomas Thrainer
    output of the command `drbd show`.
366 5520d04d Thomas Thrainer

367 5520d04d Thomas Thrainer
    """
368 5520d04d Thomas Thrainer
    if cls._PARSE_SHOW is None:
369 5520d04d Thomas Thrainer
      cls._PARSE_SHOW = cls._ConstructShowParser()
370 5520d04d Thomas Thrainer
371 5520d04d Thomas Thrainer
    return cls._PARSE_SHOW
372 5520d04d Thomas Thrainer
373 5520d04d Thomas Thrainer
  @classmethod
374 5520d04d Thomas Thrainer
  def _ConstructShowParser(cls):
375 5520d04d Thomas Thrainer
    raise NotImplementedError
376 5520d04d Thomas Thrainer
377 5520d04d Thomas Thrainer
378 5520d04d Thomas Thrainer
class DRBD83ShowInfo(BaseShowInfo):
379 5520d04d Thomas Thrainer
  @classmethod
380 5520d04d Thomas Thrainer
  def _ConstructShowParser(cls):
381 5520d04d Thomas Thrainer
    # an entire section
382 5520d04d Thomas Thrainer
    section_name = pyp.Word(pyp.alphas + "_")
383 5520d04d Thomas Thrainer
    section = section_name + \
384 5520d04d Thomas Thrainer
              cls._lbrace + \
385 5520d04d Thomas Thrainer
              pyp.ZeroOrMore(pyp.Group(cls._stmt)) + \
386 5520d04d Thomas Thrainer
              cls._rbrace
387 5520d04d Thomas Thrainer
388 5520d04d Thomas Thrainer
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ cls._stmt))
389 5520d04d Thomas Thrainer
    bnf.ignore(cls._comment)
390 5520d04d Thomas Thrainer
391 5520d04d Thomas Thrainer
    return bnf
392 5520d04d Thomas Thrainer
393 5520d04d Thomas Thrainer
  @classmethod
394 5520d04d Thomas Thrainer
  def _TransformParseResult(cls, parse_result):
395 5520d04d Thomas Thrainer
    retval = {}
396 5520d04d Thomas Thrainer
    for section in parse_result:
397 27d69b25 Thomas Thrainer
      sname = section[0]
398 27d69b25 Thomas Thrainer
      if sname == "_this_host":
399 27d69b25 Thomas Thrainer
        for lst in section[1:]:
400 27d69b25 Thomas Thrainer
          if lst[0] == "disk":
401 27d69b25 Thomas Thrainer
            retval["local_dev"] = lst[1]
402 27d69b25 Thomas Thrainer
          elif lst[0] == "meta-disk":
403 27d69b25 Thomas Thrainer
            retval["meta_dev"] = lst[1]
404 27d69b25 Thomas Thrainer
            retval["meta_index"] = lst[2]
405 27d69b25 Thomas Thrainer
          elif lst[0] == "address":
406 27d69b25 Thomas Thrainer
            retval["local_addr"] = tuple(lst[1:])
407 27d69b25 Thomas Thrainer
      elif sname == "_remote_host":
408 27d69b25 Thomas Thrainer
        for lst in section[1:]:
409 27d69b25 Thomas Thrainer
          if lst[0] == "address":
410 27d69b25 Thomas Thrainer
            retval["remote_addr"] = tuple(lst[1:])
411 27d69b25 Thomas Thrainer
    return retval
412 5520d04d Thomas Thrainer
413 5520d04d Thomas Thrainer
414 5520d04d Thomas Thrainer
class DRBD84ShowInfo(BaseShowInfo):
415 5520d04d Thomas Thrainer
  @classmethod
416 5520d04d Thomas Thrainer
  def _ConstructShowParser(cls):
417 5520d04d Thomas Thrainer
    # an entire section (sections can be nested in DRBD 8.4, and there exist
418 5520d04d Thomas Thrainer
    # sections like "volume 0")
419 5520d04d Thomas Thrainer
    section_name = pyp.Word(pyp.alphas + "_") + \
420 5520d04d Thomas Thrainer
                   pyp.Optional(pyp.Word(pyp.nums)).suppress() # skip volume idx
421 5520d04d Thomas Thrainer
    section = pyp.Forward()
422 5520d04d Thomas Thrainer
    # pylint: disable=W0106
423 5520d04d Thomas Thrainer
    section << (section_name +
424 5520d04d Thomas Thrainer
                cls._lbrace +
425 5520d04d Thomas Thrainer
                pyp.ZeroOrMore(pyp.Group(cls._stmt ^ section)) +
426 5520d04d Thomas Thrainer
                cls._rbrace)
427 5520d04d Thomas Thrainer
428 5520d04d Thomas Thrainer
    resource_name = pyp.Word(pyp.alphanums + "_-.")
429 5520d04d Thomas Thrainer
    resource = (pyp.Literal("resource") + resource_name).suppress() + \
430 5520d04d Thomas Thrainer
               cls._lbrace + \
431 5520d04d Thomas Thrainer
               pyp.ZeroOrMore(pyp.Group(section)) + \
432 5520d04d Thomas Thrainer
               cls._rbrace
433 5520d04d Thomas Thrainer
434 5520d04d Thomas Thrainer
    resource.ignore(cls._comment)
435 5520d04d Thomas Thrainer
436 5520d04d Thomas Thrainer
    return resource
437 5520d04d Thomas Thrainer
438 5520d04d Thomas Thrainer
  @classmethod
439 8f8442d6 Thomas Thrainer
  def _TransformVolumeSection(cls, vol_content, retval):
440 8f8442d6 Thomas Thrainer
    for entry in vol_content:
441 8f8442d6 Thomas Thrainer
      if entry[0] == "disk" and len(entry) == 2 and \
442 8f8442d6 Thomas Thrainer
          isinstance(entry[1], basestring):
443 8f8442d6 Thomas Thrainer
        retval["local_dev"] = entry[1]
444 8f8442d6 Thomas Thrainer
      elif entry[0] == "meta-disk":
445 8f8442d6 Thomas Thrainer
        if len(entry) > 1:
446 8f8442d6 Thomas Thrainer
          retval["meta_dev"] = entry[1]
447 8f8442d6 Thomas Thrainer
        if len(entry) > 2:
448 8f8442d6 Thomas Thrainer
          retval["meta_index"] = entry[2]
449 8f8442d6 Thomas Thrainer
450 8f8442d6 Thomas Thrainer
  @classmethod
451 5520d04d Thomas Thrainer
  def _TransformParseResult(cls, parse_result):
452 5520d04d Thomas Thrainer
    retval = {}
453 5520d04d Thomas Thrainer
    for section in parse_result:
454 5520d04d Thomas Thrainer
      sname = section[0]
455 5520d04d Thomas Thrainer
      if sname == "_this_host":
456 5520d04d Thomas Thrainer
        for lst in section[1:]:
457 5520d04d Thomas Thrainer
          if lst[0] == "address":
458 5520d04d Thomas Thrainer
            retval["local_addr"] = tuple(lst[1:])
459 5520d04d Thomas Thrainer
          elif lst[0] == "volume":
460 8f8442d6 Thomas Thrainer
            cls._TransformVolumeSection(lst[1:], retval)
461 5520d04d Thomas Thrainer
      elif sname == "_remote_host":
462 5520d04d Thomas Thrainer
        for lst in section[1:]:
463 5520d04d Thomas Thrainer
          if lst[0] == "address":
464 5520d04d Thomas Thrainer
            retval["remote_addr"] = tuple(lst[1:])
465 5520d04d Thomas Thrainer
    return retval