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+)(?:\.(\d+))?"
168 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
169 _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
170
174
176 """Return the DRBD version.
177
178 This will return a dict with keys:
179 - k_major
180 - k_minor
181 - k_point
182 - k_fix (only on some drbd versions)
183 - api
184 - proto
185 - proto2 (only on drbd > 8.2.X)
186
187 """
188 return self._version
189
191 """Return the DRBD version as a single string.
192
193 """
194 version = self.GetVersion()
195 retval = "%d.%d.%d" % \
196 (version["k_major"], version["k_minor"], version["k_point"])
197 if "k_fix" in version:
198 retval += ".%s" % version["k_fix"]
199
200 retval += " (api:%d/proto:%d" % (version["api"], version["proto"])
201 if "proto2" in version:
202 retval += "-%s" % version["proto2"]
203 retval += ")"
204 return retval
205
207 """Return a list of minor for which information is available.
208
209 This list is ordered in exactly the order which was found in the underlying
210 data.
211
212 """
213 return self._minors
214
216 return minor in self._line_per_minor
217
220
222 first_line = lines[0].strip()
223 version = self._VERSION_RE.match(first_line)
224 if not version:
225 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
226 first_line)
227
228 values = version.groups()
229 retval = {
230 "k_major": int(values[0]),
231 "k_minor": int(values[1]),
232 "k_point": int(values[2]),
233 "api": int(values[4]),
234 "proto": int(values[5]),
235 }
236 if values[3] is not None:
237 retval["k_fix"] = values[3]
238 if values[6] is not None:
239 retval["proto2"] = values[6]
240
241 return retval
242
244 """Transform the raw lines into a dictionary based on the minor.
245
246 @return: a dictionary of minor: joined lines from /proc/drbd
247 for that minor
248
249 """
250 minors = []
251 results = {}
252 old_minor = old_line = None
253 for line in lines:
254 if not line:
255 continue
256 lresult = self._VALID_LINE_RE.match(line)
257 if lresult is not None:
258 if old_minor is not None:
259 minors.append(old_minor)
260 results[old_minor] = old_line
261 old_minor = int(lresult.group(1))
262 old_line = line
263 else:
264 if old_minor is not None:
265 old_line += " " + line.strip()
266
267 if old_minor is not None:
268 minors.append(old_minor)
269 results[old_minor] = old_line
270 return minors, results
271
272 @staticmethod
275
276 @staticmethod
278 try:
279 lines = utils.ReadFile(filename).splitlines()
280 except EnvironmentError, err:
281 if err.errno == errno.ENOENT:
282 base.ThrowError("The file %s cannot be opened, check if the module"
283 " is loaded (%s)", filename, str(err))
284 else:
285 base.ThrowError("Can't read the DRBD proc file %s: %s",
286 filename, str(err))
287 if not lines:
288 base.ThrowError("Can't read any data from %s", filename)
289 return DRBD8Info.CreateFromLines(lines)
290
293 """Base class for parsing the `drbdsetup show` output.
294
295 Holds various common pyparsing expressions which are used by subclasses. Also
296 provides caching of the constructed parser.
297
298 """
299 _PARSE_SHOW = None
300
301
302 _lbrace = pyp.Literal("{").suppress()
303 _rbrace = pyp.Literal("}").suppress()
304 _lbracket = pyp.Literal("[").suppress()
305 _rbracket = pyp.Literal("]").suppress()
306 _semi = pyp.Literal(";").suppress()
307 _colon = pyp.Literal(":").suppress()
308
309 _number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
310
311 _comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
312 _defa = pyp.Literal("_is_default").suppress()
313 _dbl_quote = pyp.Literal('"').suppress()
314
315 _keyword = pyp.Word(pyp.alphanums + "-")
316
317
318 _value = pyp.Word(pyp.alphanums + "_-/.:")
319 _quoted = _dbl_quote + pyp.CharsNotIn('"') + _dbl_quote
320 _ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
321 pyp.Word(pyp.nums + ".") + _colon + _number)
322 _ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
323 pyp.Optional(_lbracket) + pyp.Word(pyp.hexnums + ":") +
324 pyp.Optional(_rbracket) + _colon + _number)
325
326 _meta_value = ((_value ^ _quoted) + _lbracket + _number + _rbracket)
327
328 _device_value = pyp.Literal("minor").suppress() + _number
329
330
331 _stmt = (~_rbrace + _keyword + ~_lbrace +
332 pyp.Optional(_ipv4_addr ^ _ipv6_addr ^ _value ^ _quoted ^
333 _meta_value ^ _device_value) +
334 pyp.Optional(_defa) + _semi +
335 pyp.Optional(pyp.restOfLine).suppress())
336
337 @classmethod
339 """Parse details about a given DRBD minor.
340
341 This returns, if available, the local backing device (as a path)
342 and the local and remote (ip, port) information from a string
343 containing the output of the `drbdsetup show` command as returned
344 by DRBD8Dev._GetShowData.
345
346 This will return a dict with keys:
347 - local_dev
348 - meta_dev
349 - meta_index
350 - local_addr
351 - remote_addr
352
353 """
354 if not show_data:
355 return {}
356
357 try:
358
359 results = (cls._GetShowParser()).parseString(show_data)
360 except pyp.ParseException, err:
361 base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
362
363 return cls._TransformParseResult(results)
364
365 @classmethod
368
369 @classmethod
371 """Return a parser for `drbd show` output.
372
373 This will either create or return an already-created parser for the
374 output of the command `drbd show`.
375
376 """
377 if cls._PARSE_SHOW is None:
378 cls._PARSE_SHOW = cls._ConstructShowParser()
379
380 return cls._PARSE_SHOW
381
382 @classmethod
384 raise NotImplementedError
385
388 @classmethod
390
391 section_name = pyp.Word(pyp.alphas + "_")
392 section = section_name + \
393 cls._lbrace + \
394 pyp.ZeroOrMore(pyp.Group(cls._stmt)) + \
395 cls._rbrace
396
397 bnf = pyp.ZeroOrMore(pyp.Group(section ^ cls._stmt))
398 bnf.ignore(cls._comment)
399
400 return bnf
401
402 @classmethod
421
424 @classmethod
426
427
428 section_name = pyp.Word(pyp.alphas + "_") + \
429 pyp.Optional(pyp.Word(pyp.nums)).suppress()
430 section = pyp.Forward()
431
432 section << (section_name +
433 cls._lbrace +
434 pyp.ZeroOrMore(pyp.Group(cls._stmt ^ section)) +
435 cls._rbrace)
436
437 resource_name = pyp.Word(pyp.alphanums + "_-.")
438 resource = (pyp.Literal("resource") + resource_name).suppress() + \
439 cls._lbrace + \
440 pyp.ZeroOrMore(pyp.Group(section)) + \
441 cls._rbrace
442
443 resource.ignore(cls._comment)
444
445 return resource
446
447 @classmethod
458
459 @classmethod
475