Statistics
| Branch: | Tag: | Revision:

root / lib / storage / drbd_info.py @ 577edf04

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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