Revision d01e51a5

b/lib/block/drbd.py
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"
b/test/py/ganeti.block.bdev_unittest.py
104 104

  
105 105
  def testParserCreation(self):
106 106
    """Test drbdsetup show parser creation"""
107
    drbd.DRBD8._GetShowParser()
107
    drbd.DRBD8ShowInfo._GetShowParser()
108 108

  
109 109
  def testParser80(self):
110 110
    """Test drbdsetup show parser for disk and network version 8.0"""
111 111
    data = testutils.ReadTestData("bdev-drbd-8.0.txt")
112
    result = drbd.DRBD8._GetDevInfo(data)
112
    result = drbd.DRBD8ShowInfo.GetDevInfo(data)
113 113
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
114 114
                                   "/dev/xenvg/test.meta"),
115 115
                    "Wrong local disk info")
......
120 120
  def testParser83(self):
121 121
    """Test drbdsetup show parser for disk and network version 8.3"""
122 122
    data = testutils.ReadTestData("bdev-drbd-8.3.txt")
123
    result = drbd.DRBD8._GetDevInfo(data)
123
    result = drbd.DRBD8ShowInfo.GetDevInfo(data)
124 124
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
125 125
                                   "/dev/xenvg/test.meta"),
126 126
                    "Wrong local disk info")
......
131 131
  def testParserNetIP4(self):
132 132
    """Test drbdsetup show parser for IPv4 network"""
133 133
    data = testutils.ReadTestData("bdev-drbd-net-ip4.txt")
134
    result = drbd.DRBD8._GetDevInfo(data)
134
    result = drbd.DRBD8ShowInfo.GetDevInfo(data)
135 135
    self.failUnless(("local_dev" not in result and
136 136
                     "meta_dev" not in result and
137 137
                     "meta_index" not in result),
......
143 143
  def testParserNetIP6(self):
144 144
    """Test drbdsetup show parser for IPv6 network"""
145 145
    data = testutils.ReadTestData("bdev-drbd-net-ip6.txt")
146
    result = drbd.DRBD8._GetDevInfo(data)
146
    result = drbd.DRBD8ShowInfo.GetDevInfo(data)
147 147
    self.failUnless(("local_dev" not in result and
148 148
                     "meta_dev" not in result and
149 149
                     "meta_index" not in result),
......
155 155
  def testParserDisk(self):
156 156
    """Test drbdsetup show parser for disk"""
157 157
    data = testutils.ReadTestData("bdev-drbd-disk.txt")
158
    result = drbd.DRBD8._GetDevInfo(data)
158
    result = drbd.DRBD8ShowInfo.GetDevInfo(data)
159 159
    self.failUnless(self._has_disk(result, "/dev/xenvg/test.data",
160 160
                                   "/dev/xenvg/test.meta"),
161 161
                    "Wrong local disk info")

Also available in: Unified diff