Revision 2fe690f1

b/lib/backend.py
832 832

  
833 833
  if constants.NV_DRBDLIST in what and vm_capable:
834 834
    try:
835
      used_minors = drbd.DRBD8.GetUsedDevs().keys()
835
      used_minors = drbd.DRBD8.GetUsedDevs()
836 836
    except errors.BlockDeviceError, err:
837 837
      logging.warning("Can't get used minors list", exc_info=True)
838 838
      used_minors = str(err)
b/lib/block/drbd.py
42 42
_DEVICE_READ_SIZE = 128 * 1024
43 43

  
44 44

  
45
class DRBD8Status(object):
45
class DRBD8Status(object): # pylint: disable=R0902
46 46
  """A DRBD status representation class.
47 47

  
48 48
  Note that this class is meant to be used to parse one of the entries returned
49
  from L{DRBD8._JoinProcDataPerMinor}.
49
  from L{DRBD8Info._JoinProcDataPerMinor}.
50 50

  
51 51
  """
52 52
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
......
119 119
    self.is_standalone = self.cstatus == self.CS_STANDALONE
120 120
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
121 121
    self.is_connected = self.cstatus == self.CS_CONNECTED
122
    self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED
122 123
    self.is_primary = self.lrole == self.RO_PRIMARY
123 124
    self.is_secondary = self.lrole == self.RO_SECONDARY
124 125
    self.peer_primary = self.rrole == self.RO_PRIMARY
......
151 152
      self.est_time = None
152 153

  
153 154

  
155
class DRBD8Info(object):
156
  """Represents information DRBD exports (usually via /proc/drbd).
157

  
158
  An instance of this class is created by one of the CreateFrom... methods.
159

  
160
  """
161

  
162
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
163
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
164
  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
165

  
166
  def __init__(self, lines):
167
    self._version = self._ParseVersion(lines)
168
    self._minors, self._line_per_minor = self._JoinProcDataPerMinor(lines)
169

  
170
  def GetVersion(self):
171
    """Return the DRBD version.
172

  
173
    This will return a dict with keys:
174
      - k_major
175
      - k_minor
176
      - k_point
177
      - api
178
      - proto
179
      - proto2 (only on drbd > 8.2.X)
180

  
181
    """
182
    return self._version
183

  
184
  def GetMinors(self):
185
    """Return a list of minor for which information is available.
186

  
187
    This list is ordered in exactly the order which was found in the underlying
188
    data.
189

  
190
    """
191
    return self._minors
192

  
193
  def HasMinorStatus(self, minor):
194
    return minor in self._line_per_minor
195

  
196
  def GetMinorStatus(self, minor):
197
    return DRBD8Status(self._line_per_minor[minor])
198

  
199
  def _ParseVersion(self, lines):
200
    first_line = lines[0].strip()
201
    version = self._VERSION_RE.match(first_line)
202
    if not version:
203
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
204
                                    first_line)
205

  
206
    values = version.groups()
207
    retval = {
208
      "k_major": int(values[0]),
209
      "k_minor": int(values[1]),
210
      "k_point": int(values[2]),
211
      "api": int(values[3]),
212
      "proto": int(values[4]),
213
      }
214
    if values[5] is not None:
215
      retval["proto2"] = values[5]
216

  
217
    return retval
218

  
219
  def _JoinProcDataPerMinor(self, lines):
220
    """Transform the raw lines into a dictionary based on the minor.
221

  
222
    @return: a dictionary of minor: joined lines from /proc/drbd
223
        for that minor
224

  
225
    """
226
    minors = []
227
    results = {}
228
    old_minor = old_line = None
229
    for line in lines:
230
      if not line: # completely empty lines, as can be returned by drbd8.0+
231
        continue
232
      lresult = self._VALID_LINE_RE.match(line)
233
      if lresult is not None:
234
        if old_minor is not None:
235
          minors.append(old_minor)
236
          results[old_minor] = old_line
237
        old_minor = int(lresult.group(1))
238
        old_line = line
239
      else:
240
        if old_minor is not None:
241
          old_line += " " + line.strip()
242
    # add last line
243
    if old_minor is not None:
244
      minors.append(old_minor)
245
      results[old_minor] = old_line
246
    return minors, results
247

  
248
  @staticmethod
249
  def CreateFromLines(lines):
250
    return DRBD8Info(lines)
251

  
252
  @staticmethod
253
  def CreateFromFile(filename=constants.DRBD_STATUS_FILE):
254
    try:
255
      lines = utils.ReadFile(filename).splitlines()
256
    except EnvironmentError, err:
257
      if err.errno == errno.ENOENT:
258
        base.ThrowError("The file %s cannot be opened, check if the module"
259
                        " is loaded (%s)", filename, str(err))
260
      else:
261
        base.ThrowError("Can't read the DRBD proc file %s: %s",
262
                        filename, str(err))
263
    if not lines:
264
      base.ThrowError("Can't read any data from %s", filename)
265
    return DRBD8Info.CreateFromLines(lines)
266

  
267

  
154 268
class DRBD8(base.BlockDev):
155 269
  """DRBD v8.x block device.
156 270

  
......
164 278
  is checked for valid size and is zeroed on create.
165 279

  
166 280
  """
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
  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
171

  
172 281
  _DRBD_MAJOR = 147
173
  _ST_UNCONFIGURED = DRBD8Status.CS_UNCONFIGURED
174
  _ST_WFCONNECTION = DRBD8Status.CS_WFCONNECTION
175
  _ST_CONNECTED = DRBD8Status.CS_CONNECTED
176 282

  
177
  _STATUS_FILE = constants.DRBD_STATUS_FILE
178 283
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
179 284

  
180 285
  _MAX_MINORS = 255
......
205 310
        children = []
206 311
    super(DRBD8, self).__init__(unique_id, children, size, params)
207 312
    self.major = self._DRBD_MAJOR
208
    version = self._GetVersion(self._GetProcData())
313

  
314
    drbd_info = DRBD8Info.CreateFromFile()
315
    version = drbd_info.GetVersion()
209 316
    if version["k_major"] != 8:
210 317
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
211 318
                      " usage: kernel is %s.%s, ganeti wants 8.x",
......
218 325
    self.Attach()
219 326

  
220 327
  @staticmethod
221
  def _GetProcData(filename=_STATUS_FILE):
222
    """Return data from /proc/drbd.
223

  
224
    """
225
    try:
226
      data = utils.ReadFile(filename).splitlines()
227
    except EnvironmentError, err:
228
      if err.errno == errno.ENOENT:
229
        base.ThrowError("The file %s cannot be opened, check if the module"
230
                        " is loaded (%s)", filename, str(err))
231
      else:
232
        base.ThrowError("Can't read the DRBD proc file %s: %s",
233
                        filename, str(err))
234
    if not data:
235
      base.ThrowError("Can't read any data from %s", filename)
236
    return data
237

  
238
  @classmethod
239
  def _JoinProcDataPerMinor(cls, data):
240
    """Transform the output of _GetProdData into a nicer form.
241

  
242
    @return: a dictionary of minor: joined lines from /proc/drbd
243
        for that minor
244

  
245
    """
246
    results = {}
247
    old_minor = old_line = None
248
    for line in data:
249
      if not line: # completely empty lines, as can be returned by drbd8.0+
250
        continue
251
      lresult = cls._VALID_LINE_RE.match(line)
252
      if lresult is not None:
253
        if old_minor is not None:
254
          results[old_minor] = old_line
255
        old_minor = int(lresult.group(1))
256
        old_line = line
257
      else:
258
        if old_minor is not None:
259
          old_line += " " + line.strip()
260
    # add last line
261
    if old_minor is not None:
262
      results[old_minor] = old_line
263
    return results
264

  
265
  @classmethod
266
  def _GetVersion(cls, proc_data):
267
    """Return the DRBD version.
268

  
269
    This will return a dict with keys:
270
      - k_major
271
      - k_minor
272
      - k_point
273
      - api
274
      - proto
275
      - proto2 (only on drbd > 8.2.X)
276

  
277
    """
278
    first_line = proc_data[0].strip()
279
    version = cls._VERSION_RE.match(first_line)
280
    if not version:
281
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
282
                                    first_line)
283

  
284
    values = version.groups()
285
    retval = {
286
      "k_major": int(values[0]),
287
      "k_minor": int(values[1]),
288
      "k_point": int(values[2]),
289
      "api": int(values[3]),
290
      "proto": int(values[4]),
291
      }
292
    if values[5] is not None:
293
      retval["proto2"] = values[5]
294

  
295
    return retval
296

  
297
  @staticmethod
298 328
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
299 329
    """Returns DRBD usermode_helper currently set.
300 330

  
......
324 354
    """Compute the list of used DRBD devices.
325 355

  
326 356
    """
327
    data = cls._GetProcData()
328

  
329
    used_devs = {}
330
    for line in data:
331
      match = cls._VALID_LINE_RE.match(line)
332
      if not match:
333
        continue
334
      minor = int(match.group(1))
335
      state = match.group(2)
336
      if state == cls._ST_UNCONFIGURED:
337
        continue
338
      used_devs[minor] = state, line
339

  
340
    return used_devs
357
    drbd_info = DRBD8Info.CreateFromFile()
358
    return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured,
359
                  drbd_info.GetMinors())
341 360

  
342 361
  def _SetFromMinor(self, minor):
343 362
    """Set our parameters based on the given minor.
......
406 425
    if result.failed:
407 426
      base.ThrowError("Can't initialize meta device: %s", result.output)
408 427

  
409
  @classmethod
410
  def _FindUnusedMinor(cls):
428
  def _FindUnusedMinor(self):
411 429
    """Find an unused DRBD device.
412 430

  
413 431
    This is specific to 8.x as the minors are allocated dynamically,
414 432
    so non-existing numbers up to a max minor count are actually free.
415 433

  
416 434
    """
417
    data = cls._GetProcData()
418 435

  
419 436
    highest = None
420
    for line in data:
421
      match = cls._UNUSED_LINE_RE.match(line)
422
      if match:
423
        return int(match.group(1))
424
      match = cls._VALID_LINE_RE.match(line)
425
      if match:
426
        minor = int(match.group(1))
427
        highest = max(highest, minor)
437
    drbd_info = DRBD8Info.CreateFromFile()
438
    for minor in drbd_info.GetMinors():
439
      status = drbd_info.GetMinorStatus(minor)
440
      if not status.is_in_use:
441
        return minor
442
      highest = max(highest, minor)
443

  
428 444
    if highest is None: # there are no minors in use at all
429 445
      return 0
430
    if highest >= cls._MAX_MINORS:
446
    if highest >= self._MAX_MINORS:
431 447
      logging.error("Error: no free drbd minors!")
432 448
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
449

  
433 450
    return highest + 1
434 451

  
435 452
  @classmethod
......
607 624
    if size:
608 625
      args.extend(["-d", "%sm" % size])
609 626

  
610
    version = self._GetVersion(self._GetProcData())
627
    drbd_info = DRBD8Info.CreateFromFile()
628
    version = drbd_info.GetVersion()
611 629
    vmaj = version["k_major"]
612 630
    vmin = version["k_minor"]
613 631
    vrel = version["k_point"]
......
822 840
    self._ShutdownLocal(self.minor)
823 841
    self._children = []
824 842

  
825
  @classmethod
826
  def _SetMinorSyncParams(cls, minor, params):
843
  def _SetMinorSyncParams(self, minor, params):
827 844
    """Set the parameters of the DRBD syncer.
828 845

  
829 846
    This is the low-level implementation.
......
837 854

  
838 855
    """
839 856

  
840
    args = ["drbdsetup", cls._DevPath(minor), "syncer"]
857
    args = ["drbdsetup", self._DevPath(minor), "syncer"]
841 858
    if params[constants.LDP_DYNAMIC_RESYNC]:
842
      version = cls._GetVersion(cls._GetProcData())
859
      drbd_info = DRBD8Info.CreateFromFile()
860
      version = drbd_info.GetVersion()
843 861
      vmin = version["k_minor"]
844 862
      vrel = version["k_point"]
845 863

  
......
929 947
    if self.minor is None:
930 948
      base.ThrowError("drbd%d: GetStats() called while not attached",
931 949
                      self._aminor)
932
    proc_info = self._JoinProcDataPerMinor(self._GetProcData())
933
    if self.minor not in proc_info:
950
    drbd_info = DRBD8Info.CreateFromFile()
951
    if not drbd_info.HasMinorStatus(self.minor):
934 952
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
935
    return DRBD8Status(proc_info[self.minor])
953
    return drbd_info.GetMinorStatus(self.minor)
936 954

  
937 955
  def GetSyncStatus(self):
938 956
    """Returns the sync status of the device.
......
1312 1330
                                   " exclusive_storage")
1313 1331
    # check that the minor is unused
1314 1332
    aminor = unique_id[4]
1315
    proc_info = cls._JoinProcDataPerMinor(cls._GetProcData())
1316
    if aminor in proc_info:
1317
      status = DRBD8Status(proc_info[aminor])
1333

  
1334
    drbd_info = DRBD8Info.CreateFromFile()
1335
    if drbd_info.HasMinorStatus(aminor):
1336
      status = drbd_info.GetMinorStatus(aminor)
1318 1337
      in_use = status.is_in_use
1319 1338
    else:
1320 1339
      in_use = False
b/lib/watcher/nodemaint.py
80 80
    """Get list of used DRBD minors.
81 81

  
82 82
    """
83
    return drbd.DRBD8.GetUsedDevs().keys()
83
    return drbd.DRBD8.GetUsedDevs()
84 84

  
85 85
  @classmethod
86 86
  def DoMaintenance(cls, role):
b/test/data/proc_drbd80-emptyline.txt
1
version: 8.0.12 (api:86/proto:86)
1 2
GIT-hash: 5c9f89594553e32adb87d9638dce591782f947e3 build by root@node1.example.com, 2009-05-22 12:47:52
2 3
 0: cs:Connected st:Primary/Secondary ds:UpToDate/UpToDate C r---
3 4
    ns:78728316 nr:0 dw:77675644 dr:1277039 al:254 bm:270 lo:0 pe:0 ua:0 ap:0
b/test/hs/Test/Ganeti/Block/Drbd/Parser.hs
52 52
case_drbd80_emptyline :: Assertion
53 53
case_drbd80_emptyline = testFile "proc_drbd80-emptyline.txt" $
54 54
  DRBDStatus
55
    ( VersionInfo Nothing Nothing Nothing Nothing
55
    ( VersionInfo (Just "8.0.12") (Just "86") (Just "86") Nothing
56 56
        (Just "5c9f89594553e32adb87d9638dce591782f947e3")
57 57
        (Just "root@node1.example.com, 2009-05-22 12:47:52")
58 58
    )
b/test/py/ganeti.block.bdev_unittest.py
71 71
      }
72 72
    ]
73 73
    for d,r in zip(data, result):
74
      self.assertEqual(drbd.DRBD8._GetVersion(d), r)
74
      info = drbd.DRBD8Info.CreateFromLines(d)
75
      self.assertEqual(info.GetVersion(), r)
75 76

  
76 77

  
77 78
class TestDRBD8Runner(testutils.GanetiTestCase):
......
244 245
    proc83_sync_data = testutils.TestDataFilename("proc_drbd83_sync.txt")
245 246
    proc83_sync_krnl_data = \
246 247
      testutils.TestDataFilename("proc_drbd83_sync_krnl2.6.39.txt")
247
    self.proc_data = drbd.DRBD8._GetProcData(filename=proc_data)
248
    self.proc80e_data = drbd.DRBD8._GetProcData(filename=proc80e_data)
249
    self.proc83_data = drbd.DRBD8._GetProcData(filename=proc83_data)
250
    self.proc83_sync_data = drbd.DRBD8._GetProcData(filename=proc83_sync_data)
251
    self.proc83_sync_krnl_data = \
252
      drbd.DRBD8._GetProcData(filename=proc83_sync_krnl_data)
253
    self.mass_data = drbd.DRBD8._JoinProcDataPerMinor(self.proc_data)
254
    self.mass80e_data = drbd.DRBD8._JoinProcDataPerMinor(self.proc80e_data)
255
    self.mass83_data = drbd.DRBD8._JoinProcDataPerMinor(self.proc83_data)
256
    self.mass83_sync_data = \
257
      drbd.DRBD8._JoinProcDataPerMinor(self.proc83_sync_data)
258
    self.mass83_sync_krnl_data = \
259
      drbd.DRBD8._JoinProcDataPerMinor(self.proc83_sync_krnl_data)
248

  
249
    self.drbd_info = drbd.DRBD8Info.CreateFromFile(filename=proc_data)
250
    self.drbd_info80e = drbd.DRBD8Info.CreateFromFile(filename=proc80e_data)
251
    self.drbd_info83 = drbd.DRBD8Info.CreateFromFile(filename=proc83_data)
252
    self.drbd_info83_sync = \
253
      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_data)
254
    self.drbd_info83_sync_krnl = \
255
      drbd.DRBD8Info.CreateFromFile(filename=proc83_sync_krnl_data)
260 256

  
261 257
  def testIOErrors(self):
262 258
    """Test handling of errors while reading the proc file."""
263 259
    temp_file = self._CreateTempFile()
264 260
    os.unlink(temp_file)
265 261
    self.failUnlessRaises(errors.BlockDeviceError,
266
                          drbd.DRBD8._GetProcData, filename=temp_file)
262
                          drbd.DRBD8Info.CreateFromFile, filename=temp_file)
267 263

  
268 264
  def testHelper(self):
269 265
    """Test reading usermode_helper in /sys."""
......
280 276

  
281 277
  def testMinorNotFound(self):
282 278
    """Test not-found-minor in /proc"""
283
    self.failUnless(9 not in self.mass_data)
284
    self.failUnless(9 not in self.mass83_data)
285
    self.failUnless(3 not in self.mass80e_data)
279
    self.failUnless(not self.drbd_info.HasMinorStatus(9))
280
    self.failUnless(not self.drbd_info83.HasMinorStatus(9))
281
    self.failUnless(not self.drbd_info80e.HasMinorStatus(3))
286 282

  
287 283
  def testLineNotMatch(self):
288 284
    """Test wrong line passed to drbd.DRBD8Status"""
......
290 286

  
291 287
  def testMinor0(self):
292 288
    """Test connected, primary device"""
293
    for data in [self.mass_data, self.mass83_data]:
294
      stats = drbd.DRBD8Status(data[0])
289
    for info in [self.drbd_info, self.drbd_info83]:
290
      stats = info.GetMinorStatus(0)
295 291
      self.failUnless(stats.is_in_use)
296 292
      self.failUnless(stats.is_connected and stats.is_primary and
297 293
                      stats.peer_secondary and stats.is_disk_uptodate)
298 294

  
299 295
  def testMinor1(self):
300 296
    """Test connected, secondary device"""
301
    for data in [self.mass_data, self.mass83_data]:
302
      stats = drbd.DRBD8Status(data[1])
297
    for info in [self.drbd_info, self.drbd_info83]:
298
      stats = info.GetMinorStatus(1)
303 299
      self.failUnless(stats.is_in_use)
304 300
      self.failUnless(stats.is_connected and stats.is_secondary and
305 301
                      stats.peer_primary and stats.is_disk_uptodate)
306 302

  
307 303
  def testMinor2(self):
308 304
    """Test unconfigured device"""
309
    for data in [self.mass_data, self.mass83_data, self.mass80e_data]:
310
      stats = drbd.DRBD8Status(data[2])
305
    for info in [self.drbd_info, self.drbd_info83, self.drbd_info80e]:
306
      stats = info.GetMinorStatus(2)
311 307
      self.failIf(stats.is_in_use)
312 308

  
313 309
  def testMinor4(self):
314 310
    """Test WFconn device"""
315
    for data in [self.mass_data, self.mass83_data]:
316
      stats = drbd.DRBD8Status(data[4])
311
    for info in [self.drbd_info, self.drbd_info83]:
312
      stats = info.GetMinorStatus(4)
317 313
      self.failUnless(stats.is_in_use)
318 314
      self.failUnless(stats.is_wfconn and stats.is_primary and
319 315
                      stats.rrole == "Unknown" and
......
321 317

  
322 318
  def testMinor6(self):
323 319
    """Test diskless device"""
324
    for data in [self.mass_data, self.mass83_data]:
325
      stats = drbd.DRBD8Status(data[6])
320
    for info in [self.drbd_info, self.drbd_info83]:
321
      stats = info.GetMinorStatus(6)
326 322
      self.failUnless(stats.is_in_use)
327 323
      self.failUnless(stats.is_connected and stats.is_secondary and
328 324
                      stats.peer_primary and stats.is_diskless)
329 325

  
330 326
  def testMinor8(self):
331 327
    """Test standalone device"""
332
    for data in [self.mass_data, self.mass83_data]:
333
      stats = drbd.DRBD8Status(data[8])
328
    for info in [self.drbd_info, self.drbd_info83]:
329
      stats = info.GetMinorStatus(8)
334 330
      self.failUnless(stats.is_in_use)
335 331
      self.failUnless(stats.is_standalone and
336 332
                      stats.rrole == "Unknown" and
337 333
                      stats.is_disk_uptodate)
338 334

  
339 335
  def testDRBD83SyncFine(self):
340
    stats = drbd.DRBD8Status(self.mass83_sync_data[3])
336
    stats = self.drbd_info83_sync.GetMinorStatus(3)
341 337
    self.failUnless(stats.is_in_resync)
342 338
    self.failUnless(stats.sync_percent is not None)
343 339

  
344 340
  def testDRBD83SyncBroken(self):
345
    stats = drbd.DRBD8Status(self.mass83_sync_krnl_data[3])
341
    stats = self.drbd_info83_sync_krnl.GetMinorStatus(3)
346 342
    self.failUnless(stats.is_in_resync)
347 343
    self.failUnless(stats.sync_percent is not None)
348 344

  

Also available in: Unified diff