Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd_info.py @ 30b12688

History | View | Annotate | Download (14.3 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 27d69b25 Thomas Thrainer
146 27d69b25 Thomas Thrainer
class DRBD8Info(object):
147 27d69b25 Thomas Thrainer
  """Represents information DRBD exports (usually via /proc/drbd).
148 27d69b25 Thomas Thrainer

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

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

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

173 27d69b25 Thomas Thrainer
    """
174 27d69b25 Thomas Thrainer
    return self._version
175 27d69b25 Thomas Thrainer
176 5bb0a1cb Thomas Thrainer
  def GetVersionString(self):
177 5bb0a1cb Thomas Thrainer
    """Return the DRBD version as a single string.
178 5bb0a1cb Thomas Thrainer

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

195 27d69b25 Thomas Thrainer
    This list is ordered in exactly the order which was found in the underlying
196 27d69b25 Thomas Thrainer
    data.
197 27d69b25 Thomas Thrainer

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

232 27d69b25 Thomas Thrainer
    @return: a dictionary of minor: joined lines from /proc/drbd
233 27d69b25 Thomas Thrainer
        for that minor
234 27d69b25 Thomas Thrainer

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

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

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

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

332 27d69b25 Thomas Thrainer
    This will return a dict with keys:
333 27d69b25 Thomas Thrainer
      - local_dev
334 27d69b25 Thomas Thrainer
      - meta_dev
335 27d69b25 Thomas Thrainer
      - meta_index
336 27d69b25 Thomas Thrainer
      - local_addr
337 27d69b25 Thomas Thrainer
      - remote_addr
338 27d69b25 Thomas Thrainer

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

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

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