265 |
265 |
return DRBD8Info.CreateFromLines(lines)
|
266 |
266 |
|
267 |
267 |
|
|
268 |
class DRBD8ShowInfo(object):
|
|
269 |
"""Helper class which parses the output of drbdsetup show
|
|
270 |
|
|
271 |
"""
|
|
272 |
_PARSE_SHOW = None
|
|
273 |
|
|
274 |
@classmethod
|
|
275 |
def _GetShowParser(cls):
|
|
276 |
"""Return a parser for `drbd show` output.
|
|
277 |
|
|
278 |
This will either create or return an already-created parser for the
|
|
279 |
output of the command `drbd show`.
|
|
280 |
|
|
281 |
"""
|
|
282 |
if cls._PARSE_SHOW is not None:
|
|
283 |
return cls._PARSE_SHOW
|
|
284 |
|
|
285 |
# pyparsing setup
|
|
286 |
lbrace = pyp.Literal("{").suppress()
|
|
287 |
rbrace = pyp.Literal("}").suppress()
|
|
288 |
lbracket = pyp.Literal("[").suppress()
|
|
289 |
rbracket = pyp.Literal("]").suppress()
|
|
290 |
semi = pyp.Literal(";").suppress()
|
|
291 |
colon = pyp.Literal(":").suppress()
|
|
292 |
# this also converts the value to an int
|
|
293 |
number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
|
|
294 |
|
|
295 |
comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
|
|
296 |
defa = pyp.Literal("_is_default").suppress()
|
|
297 |
dbl_quote = pyp.Literal('"').suppress()
|
|
298 |
|
|
299 |
keyword = pyp.Word(pyp.alphanums + "-")
|
|
300 |
|
|
301 |
# value types
|
|
302 |
value = pyp.Word(pyp.alphanums + "_-/.:")
|
|
303 |
quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
|
|
304 |
ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
|
|
305 |
pyp.Word(pyp.nums + ".") + colon + number)
|
|
306 |
ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
|
|
307 |
pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
|
|
308 |
pyp.Optional(rbracket) + colon + number)
|
|
309 |
# meta device, extended syntax
|
|
310 |
meta_value = ((value ^ quoted) + lbracket + number + rbracket)
|
|
311 |
# device name, extended syntax
|
|
312 |
device_value = pyp.Literal("minor").suppress() + number
|
|
313 |
|
|
314 |
# a statement
|
|
315 |
stmt = (~rbrace + keyword + ~lbrace +
|
|
316 |
pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
|
|
317 |
device_value) +
|
|
318 |
pyp.Optional(defa) + semi +
|
|
319 |
pyp.Optional(pyp.restOfLine).suppress())
|
|
320 |
|
|
321 |
# an entire section
|
|
322 |
section_name = pyp.Word(pyp.alphas + "_")
|
|
323 |
section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
|
|
324 |
|
|
325 |
bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
|
|
326 |
bnf.ignore(comment)
|
|
327 |
|
|
328 |
cls._PARSE_SHOW = bnf
|
|
329 |
|
|
330 |
return bnf
|
|
331 |
|
|
332 |
@classmethod
|
|
333 |
def GetDevInfo(cls, show_data):
|
|
334 |
"""Parse details about a given DRBD minor.
|
|
335 |
|
|
336 |
This returns, if available, the local backing device (as a path)
|
|
337 |
and the local and remote (ip, port) information from a string
|
|
338 |
containing the output of the `drbdsetup show` command as returned
|
|
339 |
by DRBD8._GetShowData.
|
|
340 |
|
|
341 |
This will return a dict with keys:
|
|
342 |
- local_dev
|
|
343 |
- meta_dev
|
|
344 |
- meta_index
|
|
345 |
- local_addr
|
|
346 |
- remote_addr
|
|
347 |
|
|
348 |
"""
|
|
349 |
retval = {}
|
|
350 |
if not show_data:
|
|
351 |
return retval
|
|
352 |
|
|
353 |
try:
|
|
354 |
# run pyparse
|
|
355 |
results = (cls._GetShowParser()).parseString(show_data)
|
|
356 |
except pyp.ParseException, err:
|
|
357 |
base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
|
|
358 |
|
|
359 |
# and massage the results into our desired format
|
|
360 |
for section in results:
|
|
361 |
sname = section[0]
|
|
362 |
if sname == "_this_host":
|
|
363 |
for lst in section[1:]:
|
|
364 |
if lst[0] == "disk":
|
|
365 |
retval["local_dev"] = lst[1]
|
|
366 |
elif lst[0] == "meta-disk":
|
|
367 |
retval["meta_dev"] = lst[1]
|
|
368 |
retval["meta_index"] = lst[2]
|
|
369 |
elif lst[0] == "address":
|
|
370 |
retval["local_addr"] = tuple(lst[1:])
|
|
371 |
elif sname == "_remote_host":
|
|
372 |
for lst in section[1:]:
|
|
373 |
if lst[0] == "address":
|
|
374 |
retval["remote_addr"] = tuple(lst[1:])
|
|
375 |
return retval
|
|
376 |
|
|
377 |
|
268 |
378 |
class DRBD8(base.BlockDev):
|
269 |
379 |
"""DRBD v8.x block device.
|
270 |
380 |
|
... | ... | |
283 |
393 |
_USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
|
284 |
394 |
|
285 |
395 |
_MAX_MINORS = 255
|
286 |
|
_PARSE_SHOW = None
|
287 |
396 |
|
288 |
397 |
# timeout constants
|
289 |
398 |
_NET_RECONFIG_TIMEOUT = 60
|
... | ... | |
450 |
559 |
return highest + 1
|
451 |
560 |
|
452 |
561 |
@classmethod
|
453 |
|
def _GetShowParser(cls):
|
454 |
|
"""Return a parser for `drbd show` output.
|
455 |
|
|
456 |
|
This will either create or return an already-created parser for the
|
457 |
|
output of the command `drbd show`.
|
458 |
|
|
459 |
|
"""
|
460 |
|
if cls._PARSE_SHOW is not None:
|
461 |
|
return cls._PARSE_SHOW
|
462 |
|
|
463 |
|
# pyparsing setup
|
464 |
|
lbrace = pyp.Literal("{").suppress()
|
465 |
|
rbrace = pyp.Literal("}").suppress()
|
466 |
|
lbracket = pyp.Literal("[").suppress()
|
467 |
|
rbracket = pyp.Literal("]").suppress()
|
468 |
|
semi = pyp.Literal(";").suppress()
|
469 |
|
colon = pyp.Literal(":").suppress()
|
470 |
|
# this also converts the value to an int
|
471 |
|
number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
|
472 |
|
|
473 |
|
comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
|
474 |
|
defa = pyp.Literal("_is_default").suppress()
|
475 |
|
dbl_quote = pyp.Literal('"').suppress()
|
476 |
|
|
477 |
|
keyword = pyp.Word(pyp.alphanums + "-")
|
478 |
|
|
479 |
|
# value types
|
480 |
|
value = pyp.Word(pyp.alphanums + "_-/.:")
|
481 |
|
quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
|
482 |
|
ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
|
483 |
|
pyp.Word(pyp.nums + ".") + colon + number)
|
484 |
|
ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
|
485 |
|
pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
|
486 |
|
pyp.Optional(rbracket) + colon + number)
|
487 |
|
# meta device, extended syntax
|
488 |
|
meta_value = ((value ^ quoted) + lbracket + number + rbracket)
|
489 |
|
# device name, extended syntax
|
490 |
|
device_value = pyp.Literal("minor").suppress() + number
|
491 |
|
|
492 |
|
# a statement
|
493 |
|
stmt = (~rbrace + keyword + ~lbrace +
|
494 |
|
pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
|
495 |
|
device_value) +
|
496 |
|
pyp.Optional(defa) + semi +
|
497 |
|
pyp.Optional(pyp.restOfLine).suppress())
|
498 |
|
|
499 |
|
# an entire section
|
500 |
|
section_name = pyp.Word(pyp.alphas + "_")
|
501 |
|
section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
|
502 |
|
|
503 |
|
bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
|
504 |
|
bnf.ignore(comment)
|
505 |
|
|
506 |
|
cls._PARSE_SHOW = bnf
|
507 |
|
|
508 |
|
return bnf
|
509 |
|
|
510 |
|
@classmethod
|
511 |
562 |
def _GetShowData(cls, minor):
|
512 |
563 |
"""Return the `drbdsetup show` data for a minor.
|
513 |
564 |
|
... | ... | |
519 |
570 |
return None
|
520 |
571 |
return result.stdout
|
521 |
572 |
|
522 |
|
@classmethod
|
523 |
|
def _GetDevInfo(cls, out):
|
524 |
|
"""Parse details about a given DRBD minor.
|
525 |
|
|
526 |
|
This return, if available, the local backing device (as a path)
|
527 |
|
and the local and remote (ip, port) information from a string
|
528 |
|
containing the output of the `drbdsetup show` command as returned
|
529 |
|
by _GetShowData.
|
530 |
|
|
531 |
|
"""
|
532 |
|
data = {}
|
533 |
|
if not out:
|
534 |
|
return data
|
535 |
|
|
536 |
|
bnf = cls._GetShowParser()
|
537 |
|
# run pyparse
|
538 |
|
|
539 |
|
try:
|
540 |
|
results = bnf.parseString(out)
|
541 |
|
except pyp.ParseException, err:
|
542 |
|
base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
|
543 |
|
|
544 |
|
# and massage the results into our desired format
|
545 |
|
for section in results:
|
546 |
|
sname = section[0]
|
547 |
|
if sname == "_this_host":
|
548 |
|
for lst in section[1:]:
|
549 |
|
if lst[0] == "disk":
|
550 |
|
data["local_dev"] = lst[1]
|
551 |
|
elif lst[0] == "meta-disk":
|
552 |
|
data["meta_dev"] = lst[1]
|
553 |
|
data["meta_index"] = lst[2]
|
554 |
|
elif lst[0] == "address":
|
555 |
|
data["local_addr"] = tuple(lst[1:])
|
556 |
|
elif sname == "_remote_host":
|
557 |
|
for lst in section[1:]:
|
558 |
|
if lst[0] == "address":
|
559 |
|
data["remote_addr"] = tuple(lst[1:])
|
560 |
|
return data
|
561 |
|
|
562 |
573 |
def _MatchesLocal(self, info):
|
563 |
574 |
"""Test if our local config matches with an existing device.
|
564 |
575 |
|
... | ... | |
775 |
786 |
minor, result.fail_reason, result.output)
|
776 |
787 |
|
777 |
788 |
def _CheckNetworkConfig():
|
778 |
|
info = self._GetDevInfo(self._GetShowData(minor))
|
|
789 |
info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))
|
779 |
790 |
if not "local_addr" in info or not "remote_addr" in info:
|
780 |
791 |
raise utils.RetryAgain()
|
781 |
792 |
|
... | ... | |
797 |
808 |
self._aminor)
|
798 |
809 |
if len(devices) != 2:
|
799 |
810 |
base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
|
800 |
|
info = self._GetDevInfo(self._GetShowData(self.minor))
|
|
811 |
info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(self.minor))
|
801 |
812 |
if "local_dev" in info:
|
802 |
813 |
base.ThrowError("drbd%d: already attached to a local disk", self.minor)
|
803 |
814 |
backend, meta = devices
|
... | ... | |
820 |
831 |
base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
|
821 |
832 |
self._aminor)
|
822 |
833 |
# early return if we don't actually have backing storage
|
823 |
|
info = self._GetDevInfo(self._GetShowData(self.minor))
|
|
834 |
info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(self.minor))
|
824 |
835 |
if "local_dev" not in info:
|
825 |
836 |
return
|
826 |
837 |
if len(self._children) != 2:
|
... | ... | |
1172 |
1183 |
# pylint: disable=W0631
|
1173 |
1184 |
net_data = (self._lhost, self._lport, self._rhost, self._rport)
|
1174 |
1185 |
for minor in (self._aminor,):
|
1175 |
|
info = self._GetDevInfo(self._GetShowData(minor))
|
|
1186 |
info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))
|
1176 |
1187 |
match_l = self._MatchesLocal(info)
|
1177 |
1188 |
match_r = self._MatchesNet(info)
|
1178 |
1189 |
|
... | ... | |
1184 |
1195 |
# disk matches, but not attached to network, attach and recheck
|
1185 |
1196 |
self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
|
1186 |
1197 |
hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
|
1187 |
|
if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
|
|
1198 |
if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))):
|
1188 |
1199 |
break
|
1189 |
1200 |
else:
|
1190 |
1201 |
base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
|
... | ... | |
1194 |
1205 |
# no local disk, but network attached and it matches
|
1195 |
1206 |
self._AssembleLocal(minor, self._children[0].dev_path,
|
1196 |
1207 |
self._children[1].dev_path, self.size)
|
1197 |
|
if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
|
|
1208 |
if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))):
|
1198 |
1209 |
break
|
1199 |
1210 |
else:
|
1200 |
1211 |
base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
|
... | ... | |
1221 |
1232 |
# None)
|
1222 |
1233 |
self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
|
1223 |
1234 |
hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
|
1224 |
|
if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
|
|
1235 |
if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))):
|
1225 |
1236 |
break
|
1226 |
1237 |
else:
|
1227 |
1238 |
base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
|