Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd_info.py @ 200a6dfe

History | View | Annotate | Download (14.4 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 27d69b25 Thomas Thrainer
                       "\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 27d69b25 Thomas Thrainer
                       "(?:\s|M)"
49 27d69b25 Thomas Thrainer
                       "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 27d69b25 Thomas Thrainer
123 27d69b25 Thomas Thrainer
    self.is_in_resync = self.cstatus in self.CSET_SYNC
124 27d69b25 Thomas Thrainer
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
125 27d69b25 Thomas Thrainer
126 27d69b25 Thomas Thrainer
    m = self.SYNC_RE.match(procline)
127 27d69b25 Thomas Thrainer
    if m:
128 27d69b25 Thomas Thrainer
      self.sync_percent = float(m.group(1))
129 27d69b25 Thomas Thrainer
      hours = int(m.group(2))
130 27d69b25 Thomas Thrainer
      minutes = int(m.group(3))
131 27d69b25 Thomas Thrainer
      seconds = int(m.group(4))
132 27d69b25 Thomas Thrainer
      self.est_time = hours * 3600 + minutes * 60 + seconds
133 27d69b25 Thomas Thrainer
    else:
134 27d69b25 Thomas Thrainer
      # we have (in this if branch) no percent information, but if
135 27d69b25 Thomas Thrainer
      # we're resyncing we need to 'fake' a sync percent information,
136 27d69b25 Thomas Thrainer
      # as this is how cmdlib determines if it makes sense to wait for
137 27d69b25 Thomas Thrainer
      # resyncing or not
138 27d69b25 Thomas Thrainer
      if self.is_in_resync:
139 27d69b25 Thomas Thrainer
        self.sync_percent = 0
140 27d69b25 Thomas Thrainer
      else:
141 27d69b25 Thomas Thrainer
        self.sync_percent = None
142 27d69b25 Thomas Thrainer
      self.est_time = None
143 27d69b25 Thomas Thrainer
144 5ee24e45 Helga Velroyen
  def __repr__(self):
145 5ee24e45 Helga Velroyen
    return ("<%s: cstatus=%s, lrole=%s, rrole=%s, ldisk=%s, rdisk=%s>" %
146 5ee24e45 Helga Velroyen
            (self.__class__, self.cstatus, self.lrole, self.rrole,
147 5ee24e45 Helga Velroyen
             self.ldisk, self.rdisk))
148 5ee24e45 Helga Velroyen
149 27d69b25 Thomas Thrainer
150 27d69b25 Thomas Thrainer
class DRBD8Info(object):
151 27d69b25 Thomas Thrainer
  """Represents information DRBD exports (usually via /proc/drbd).
152 27d69b25 Thomas Thrainer

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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