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