1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """DRBD information parsing utilities"""
32
33 import errno
34 import pyparsing as pyp
35 import re
36
37 from ganeti import constants
38 from ganeti import utils
39 from ganeti import errors
40 from ganeti import compat
41 from ganeti.storage import base
45 """A DRBD status representation class.
46
47 Note that this class is meant to be used to parse one of the entries returned
48 from L{DRBD8Info._JoinLinesPerMinor}.
49
50 """
51 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
52 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
53 r"\s+ds:([^/]+)/(\S+)\s+.*$")
54 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
55
56
57 r"(?:\s|M)"
58 r"finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
59
60 CS_UNCONFIGURED = "Unconfigured"
61 CS_STANDALONE = "StandAlone"
62 CS_WFCONNECTION = "WFConnection"
63 CS_WFREPORTPARAMS = "WFReportParams"
64 CS_CONNECTED = "Connected"
65 CS_STARTINGSYNCS = "StartingSyncS"
66 CS_STARTINGSYNCT = "StartingSyncT"
67 CS_WFBITMAPS = "WFBitMapS"
68 CS_WFBITMAPT = "WFBitMapT"
69 CS_WFSYNCUUID = "WFSyncUUID"
70 CS_SYNCSOURCE = "SyncSource"
71 CS_SYNCTARGET = "SyncTarget"
72 CS_PAUSEDSYNCS = "PausedSyncS"
73 CS_PAUSEDSYNCT = "PausedSyncT"
74 CSET_SYNC = compat.UniqueFrozenset([
75 CS_WFREPORTPARAMS,
76 CS_STARTINGSYNCS,
77 CS_STARTINGSYNCT,
78 CS_WFBITMAPS,
79 CS_WFBITMAPT,
80 CS_WFSYNCUUID,
81 CS_SYNCSOURCE,
82 CS_SYNCTARGET,
83 CS_PAUSEDSYNCS,
84 CS_PAUSEDSYNCT,
85 ])
86
87 DS_DISKLESS = "Diskless"
88 DS_ATTACHING = "Attaching"
89 DS_FAILED = "Failed"
90 DS_NEGOTIATING = "Negotiating"
91 DS_INCONSISTENT = "Inconsistent"
92 DS_OUTDATED = "Outdated"
93 DS_DUNKNOWN = "DUnknown"
94 DS_CONSISTENT = "Consistent"
95 DS_UPTODATE = "UpToDate"
96
97 RO_PRIMARY = "Primary"
98 RO_SECONDARY = "Secondary"
99 RO_UNKNOWN = "Unknown"
100
102 u = self.UNCONF_RE.match(procline)
103 if u:
104 self.cstatus = self.CS_UNCONFIGURED
105 self.lrole = self.rrole = self.ldisk = self.rdisk = None
106 else:
107 m = self.LINE_RE.match(procline)
108 if not m:
109 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
110 self.cstatus = m.group(1)
111 self.lrole = m.group(2)
112 self.rrole = m.group(3)
113 self.ldisk = m.group(4)
114 self.rdisk = m.group(5)
115
116
117
118 self.is_standalone = self.cstatus == self.CS_STANDALONE
119 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
120 self.is_connected = self.cstatus == self.CS_CONNECTED
121 self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED
122 self.is_primary = self.lrole == self.RO_PRIMARY
123 self.is_secondary = self.lrole == self.RO_SECONDARY
124 self.peer_primary = self.rrole == self.RO_PRIMARY
125 self.peer_secondary = self.rrole == self.RO_SECONDARY
126 self.both_primary = self.is_primary and self.peer_primary
127 self.both_secondary = self.is_secondary and self.peer_secondary
128
129 self.is_diskless = self.ldisk == self.DS_DISKLESS
130 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
131 self.peer_disk_uptodate = self.rdisk == self.DS_UPTODATE
132
133 self.is_in_resync = self.cstatus in self.CSET_SYNC
134 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
135
136 m = self.SYNC_RE.match(procline)
137 if m:
138 self.sync_percent = float(m.group(1))
139 hours = int(m.group(2))
140 minutes = int(m.group(3))
141 seconds = int(m.group(4))
142 self.est_time = hours * 3600 + minutes * 60 + seconds
143 else:
144
145
146
147
148 if self.is_in_resync:
149 self.sync_percent = 0
150 else:
151 self.sync_percent = None
152 self.est_time = None
153
155 return ("<%s: cstatus=%s, lrole=%s, rrole=%s, ldisk=%s, rdisk=%s>" %
156 (self.__class__, self.cstatus, self.lrole, self.rrole,
157 self.ldisk, self.rdisk))
158
161 """Represents information DRBD exports (usually via /proc/drbd).
162
163 An instance of this class is created by one of the CreateFrom... methods.
164
165 """
166
167 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
168 r"(?:\.(\d+))?(?:-(\d+))?"
169 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
170 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
171
175
177 """Return the DRBD version.
178
179 This will return a dict with keys:
180 - k_major
181 - k_minor
182 - k_point
183 - k_fix (only on some drbd versions)
184 - k_release
185 - api
186 - proto
187 - proto2 (only on drbd > 8.2.X)
188
189 """
190 return self._version
191
193 """Return the DRBD version as a single string.
194
195 """
196 version = self.GetVersion()
197 retval = "%d.%d.%d" % \
198 (version["k_major"], version["k_minor"], version["k_point"])
199 if "k_fix" in version:
200 retval += ".%s" % version["k_fix"]
201 if "k_release" in version:
202 retval += "-%s" % version["k_release"]
203
204 retval += " (api:%d/proto:%d" % (version["api"], version["proto"])
205 if "proto2" in version:
206 retval += "-%s" % version["proto2"]
207 retval += ")"
208 return retval
209
211 """Return a list of minor for which information is available.
212
213 This list is ordered in exactly the order which was found in the underlying
214 data.
215
216 """
217 return self._minors
218
220 return minor in self._line_per_minor
221
224
226 first_line = lines[0].strip()
227 version = self._VERSION_RE.match(first_line)
228 if not version:
229 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
230 first_line)
231
232 values = version.groups()
233 retval = {
234 "k_major": int(values[0]),
235 "k_minor": int(values[1]),
236 "k_point": int(values[2]),
237 "api": int(values[5]),
238 "proto": int(values[6]),
239 }
240 if values[3] is not None:
241 retval["k_fix"] = values[3]
242 if values[4] is not None:
243 retval["k_release"] = values[4]
244 if values[7] is not None:
245 retval["proto2"] = values[7]
246
247 return retval
248
250 """Transform the raw lines into a dictionary based on the minor.
251
252 @return: a dictionary of minor: joined lines from /proc/drbd
253 for that minor
254
255 """
256 minors = []
257 results = {}
258 old_minor = old_line = None
259 for line in lines:
260 if not line:
261 continue
262 lresult = self._VALID_LINE_RE.match(line)
263 if lresult is not None:
264 if old_minor is not None:
265 minors.append(old_minor)
266 results[old_minor] = old_line
267 old_minor = int(lresult.group(1))
268 old_line = line
269 else:
270 if old_minor is not None:
271 old_line += " " + line.strip()
272
273 if old_minor is not None:
274 minors.append(old_minor)
275 results[old_minor] = old_line
276 return minors, results
277
278 @staticmethod
281
282 @staticmethod
284 try:
285 lines = utils.ReadFile(filename).splitlines()
286 except EnvironmentError, err:
287 if err.errno == errno.ENOENT:
288 base.ThrowError("The file %s cannot be opened, check if the module"
289 " is loaded (%s)", filename, str(err))
290 else:
291 base.ThrowError("Can't read the DRBD proc file %s: %s",
292 filename, str(err))
293 if not lines:
294 base.ThrowError("Can't read any data from %s", filename)
295 return DRBD8Info.CreateFromLines(lines)
296
299 """Base class for parsing the `drbdsetup show` output.
300
301 Holds various common pyparsing expressions which are used by subclasses. Also
302 provides caching of the constructed parser.
303
304 """
305 _PARSE_SHOW = None
306
307
308 _lbrace = pyp.Literal("{").suppress()
309 _rbrace = pyp.Literal("}").suppress()
310 _lbracket = pyp.Literal("[").suppress()
311 _rbracket = pyp.Literal("]").suppress()
312 _semi = pyp.Literal(";").suppress()
313 _colon = pyp.Literal(":").suppress()
314
315 _number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
316
317 _comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
318 _defa = pyp.Literal("_is_default").suppress()
319 _dbl_quote = pyp.Literal('"').suppress()
320
321 _keyword = pyp.Word(pyp.alphanums + "-")
322
323
324 _value = pyp.Word(pyp.alphanums + "_-/.:")
325 _quoted = _dbl_quote + pyp.CharsNotIn('"') + _dbl_quote
326 _ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
327 pyp.Word(pyp.nums + ".") + _colon + _number)
328 _ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
329 pyp.Optional(_lbracket) + pyp.Word(pyp.hexnums + ":") +
330 pyp.Optional(_rbracket) + _colon + _number)
331
332 _meta_value = ((_value ^ _quoted) + _lbracket + _number + _rbracket)
333
334 _device_value = pyp.Literal("minor").suppress() + _number
335
336
337 _stmt = (~_rbrace + _keyword + ~_lbrace +
338 pyp.Optional(_ipv4_addr ^ _ipv6_addr ^ _value ^ _quoted ^
339 _meta_value ^ _device_value) +
340 pyp.Optional(_defa) + _semi +
341 pyp.Optional(pyp.restOfLine).suppress())
342
343 @classmethod
345 """Parse details about a given DRBD minor.
346
347 This returns, if available, the local backing device (as a path)
348 and the local and remote (ip, port) information from a string
349 containing the output of the `drbdsetup show` command as returned
350 by DRBD8Dev._GetShowData.
351
352 This will return a dict with keys:
353 - local_dev
354 - meta_dev
355 - meta_index
356 - local_addr
357 - remote_addr
358
359 """
360 if not show_data:
361 return {}
362
363 try:
364
365 results = (cls._GetShowParser()).parseString(show_data)
366 except pyp.ParseException, err:
367 base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
368
369 return cls._TransformParseResult(results)
370
371 @classmethod
374
375 @classmethod
377 """Return a parser for `drbd show` output.
378
379 This will either create or return an already-created parser for the
380 output of the command `drbd show`.
381
382 """
383 if cls._PARSE_SHOW is None:
384 cls._PARSE_SHOW = cls._ConstructShowParser()
385
386 return cls._PARSE_SHOW
387
388 @classmethod
390 raise NotImplementedError
391
394 @classmethod
396
397 section_name = pyp.Word(pyp.alphas + "_")
398 section = section_name + \
399 cls._lbrace + \
400 pyp.ZeroOrMore(pyp.Group(cls._stmt)) + \
401 cls._rbrace
402
403 bnf = pyp.ZeroOrMore(pyp.Group(section ^ cls._stmt))
404 bnf.ignore(cls._comment)
405
406 return bnf
407
408 @classmethod
427
430 @classmethod
432
433
434 section_name = pyp.Word(pyp.alphas + "_") + \
435 pyp.Optional(pyp.Word(pyp.nums)).suppress()
436 section = pyp.Forward()
437
438 section << (section_name +
439 cls._lbrace +
440 pyp.ZeroOrMore(pyp.Group(cls._stmt ^ section)) +
441 cls._rbrace)
442
443 resource_name = pyp.Word(pyp.alphanums + "_-.")
444 resource = (pyp.Literal("resource") + resource_name).suppress() + \
445 cls._lbrace + \
446 pyp.ZeroOrMore(pyp.Group(section)) + \
447 cls._rbrace
448
449 resource.ignore(cls._comment)
450
451 return resource
452
453 @classmethod
464
465 @classmethod
481