Revision fd300bc7

b/lib/backend.py
841 841
  if constants.NV_DRBDHELPER in what and vm_capable:
842 842
    status = True
843 843
    try:
844
      payload = drbd.BaseDRBD.GetUsermodeHelper()
844
      payload = drbd.DRBD8.GetUsermodeHelper()
845 845
    except errors.BlockDeviceError, err:
846 846
      logging.error("Can't get DRBD usermode helper: %s", str(err))
847 847
      status = False
......
3671 3671

  
3672 3672
  """
3673 3673
  try:
3674
    return drbd.BaseDRBD.GetUsermodeHelper()
3674
    return drbd.DRBD8.GetUsermodeHelper()
3675 3675
  except errors.BlockDeviceError, err:
3676 3676
    _Fail(str(err))
3677 3677

  
b/lib/block/drbd.py
42 42
_DEVICE_READ_SIZE = 128 * 1024
43 43

  
44 44

  
45
class BaseDRBD(base.BlockDev): # pylint: disable=W0223
46
  """Base DRBD class.
45
class DRBD8Status(object):
46
  """A DRBD status representation class.
47 47

  
48
  This class contains a few bits of common functionality between the
49
  0.7 and 8.x versions of DRBD.
48
  Note that this class is meant to be used to parse one of the entries returned
49
  from L{DRBD8._JoinProcDataPerMinor}.
50

  
51
  """
52
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
53
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
54
                       "\s+ds:([^/]+)/(\S+)\s+.*$")
55
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
56
                       # Due to a bug in drbd in the kernel, introduced in
57
                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
58
                       "(?:\s|M)"
59
                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
60

  
61
  CS_UNCONFIGURED = "Unconfigured"
62
  CS_STANDALONE = "StandAlone"
63
  CS_WFCONNECTION = "WFConnection"
64
  CS_WFREPORTPARAMS = "WFReportParams"
65
  CS_CONNECTED = "Connected"
66
  CS_STARTINGSYNCS = "StartingSyncS"
67
  CS_STARTINGSYNCT = "StartingSyncT"
68
  CS_WFBITMAPS = "WFBitMapS"
69
  CS_WFBITMAPT = "WFBitMapT"
70
  CS_WFSYNCUUID = "WFSyncUUID"
71
  CS_SYNCSOURCE = "SyncSource"
72
  CS_SYNCTARGET = "SyncTarget"
73
  CS_PAUSEDSYNCS = "PausedSyncS"
74
  CS_PAUSEDSYNCT = "PausedSyncT"
75
  CSET_SYNC = compat.UniqueFrozenset([
76
    CS_WFREPORTPARAMS,
77
    CS_STARTINGSYNCS,
78
    CS_STARTINGSYNCT,
79
    CS_WFBITMAPS,
80
    CS_WFBITMAPT,
81
    CS_WFSYNCUUID,
82
    CS_SYNCSOURCE,
83
    CS_SYNCTARGET,
84
    CS_PAUSEDSYNCS,
85
    CS_PAUSEDSYNCT,
86
    ])
87

  
88
  DS_DISKLESS = "Diskless"
89
  DS_ATTACHING = "Attaching" # transient state
90
  DS_FAILED = "Failed" # transient state, next: diskless
91
  DS_NEGOTIATING = "Negotiating" # transient state
92
  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
93
  DS_OUTDATED = "Outdated"
94
  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
95
  DS_CONSISTENT = "Consistent"
96
  DS_UPTODATE = "UpToDate" # normal state
97

  
98
  RO_PRIMARY = "Primary"
99
  RO_SECONDARY = "Secondary"
100
  RO_UNKNOWN = "Unknown"
101

  
102
  def __init__(self, procline):
103
    u = self.UNCONF_RE.match(procline)
104
    if u:
105
      self.cstatus = self.CS_UNCONFIGURED
106
      self.lrole = self.rrole = self.ldisk = self.rdisk = None
107
    else:
108
      m = self.LINE_RE.match(procline)
109
      if not m:
110
        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
111
      self.cstatus = m.group(1)
112
      self.lrole = m.group(2)
113
      self.rrole = m.group(3)
114
      self.ldisk = m.group(4)
115
      self.rdisk = m.group(5)
116

  
117
    # end reading of data from the LINE_RE or UNCONF_RE
118

  
119
    self.is_standalone = self.cstatus == self.CS_STANDALONE
120
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
121
    self.is_connected = self.cstatus == self.CS_CONNECTED
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

  
132
    self.is_in_resync = self.cstatus in self.CSET_SYNC
133
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
134

  
135
    m = self.SYNC_RE.match(procline)
136
    if m:
137
      self.sync_percent = float(m.group(1))
138
      hours = int(m.group(2))
139
      minutes = int(m.group(3))
140
      seconds = int(m.group(4))
141
      self.est_time = hours * 3600 + minutes * 60 + seconds
142
    else:
143
      # we have (in this if branch) no percent information, but if
144
      # we're resyncing we need to 'fake' a sync percent information,
145
      # as this is how cmdlib determines if it makes sense to wait for
146
      # resyncing or not
147
      if self.is_in_resync:
148
        self.sync_percent = 0
149
      else:
150
        self.sync_percent = None
151
      self.est_time = None
152

  
153

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

  
157
  This implements the local host part of the DRBD device, i.e. it
158
  doesn't do anything to the supposed peer. If you need a fully
159
  connected DRBD pair, you need to use this class on both hosts.
160

  
161
  The unique_id for the drbd device is a (local_ip, local_port,
162
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
163
  two children: the data device and the meta_device. The meta device
164
  is checked for valid size and is zeroed on create.
50 165

  
51 166
  """
52 167
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
......
55 170
  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
56 171

  
57 172
  _DRBD_MAJOR = 147
58
  _ST_UNCONFIGURED = "Unconfigured"
59
  _ST_WFCONNECTION = "WFConnection"
60
  _ST_CONNECTED = "Connected"
173
  _ST_UNCONFIGURED = DRBD8Status.CS_UNCONFIGURED
174
  _ST_WFCONNECTION = DRBD8Status.CS_WFCONNECTION
175
  _ST_CONNECTED = DRBD8Status.CS_CONNECTED
61 176

  
62 177
  _STATUS_FILE = constants.DRBD_STATUS_FILE
63 178
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
64 179

  
180
  _MAX_MINORS = 255
181
  _PARSE_SHOW = None
182

  
183
  # timeout constants
184
  _NET_RECONFIG_TIMEOUT = 60
185

  
186
  # command line options for barriers
187
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
188
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
189
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
190
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
191

  
192
  def __init__(self, unique_id, children, size, params):
193
    if children and children.count(None) > 0:
194
      children = []
195
    if len(children) not in (0, 2):
196
      raise ValueError("Invalid configuration data %s" % str(children))
197
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
198
      raise ValueError("Invalid configuration data %s" % str(unique_id))
199
    (self._lhost, self._lport,
200
     self._rhost, self._rport,
201
     self._aminor, self._secret) = unique_id
202
    if children:
203
      if not _CanReadDevice(children[1].dev_path):
204
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
205
        children = []
206
    super(DRBD8, self).__init__(unique_id, children, size, params)
207
    self.major = self._DRBD_MAJOR
208
    version = self._GetVersion(self._GetProcData())
209
    if version["k_major"] != 8:
210
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
211
                      " usage: kernel is %s.%s, ganeti wants 8.x",
212
                      version["k_major"], version["k_minor"])
213

  
214
    if (self._lhost is not None and self._lhost == self._rhost and
215
            self._lport == self._rport):
216
      raise ValueError("Invalid configuration data, same local/remote %s" %
217
                       (unique_id,))
218
    self.Attach()
219

  
65 220
  @staticmethod
66 221
  def _GetProcData(filename=_STATUS_FILE):
67 222
    """Return data from /proc/drbd.
......
228 383
      base.ThrowError("Meta device too big (%.2fMiB)",
229 384
                      (num_bytes / 1024 / 1024))
230 385

  
231
  def Rename(self, new_id):
232
    """Rename a device.
233

  
234
    This is not supported for drbd devices.
235

  
236
    """
237
    raise errors.ProgrammerError("Can't rename a drbd device")
238

  
239

  
240
class DRBD8Status(object):
241
  """A DRBD status representation class.
242

  
243
  Note that this doesn't support unconfigured devices (cs:Unconfigured).
244

  
245
  """
246
  UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
247
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
248
                       "\s+ds:([^/]+)/(\S+)\s+.*$")
249
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
250
                       # Due to a bug in drbd in the kernel, introduced in
251
                       # commit 4b0715f096 (still unfixed as of 2011-08-22)
252
                       "(?:\s|M)"
253
                       "finish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
254

  
255
  CS_UNCONFIGURED = "Unconfigured"
256
  CS_STANDALONE = "StandAlone"
257
  CS_WFCONNECTION = "WFConnection"
258
  CS_WFREPORTPARAMS = "WFReportParams"
259
  CS_CONNECTED = "Connected"
260
  CS_STARTINGSYNCS = "StartingSyncS"
261
  CS_STARTINGSYNCT = "StartingSyncT"
262
  CS_WFBITMAPS = "WFBitMapS"
263
  CS_WFBITMAPT = "WFBitMapT"
264
  CS_WFSYNCUUID = "WFSyncUUID"
265
  CS_SYNCSOURCE = "SyncSource"
266
  CS_SYNCTARGET = "SyncTarget"
267
  CS_PAUSEDSYNCS = "PausedSyncS"
268
  CS_PAUSEDSYNCT = "PausedSyncT"
269
  CSET_SYNC = compat.UniqueFrozenset([
270
    CS_WFREPORTPARAMS,
271
    CS_STARTINGSYNCS,
272
    CS_STARTINGSYNCT,
273
    CS_WFBITMAPS,
274
    CS_WFBITMAPT,
275
    CS_WFSYNCUUID,
276
    CS_SYNCSOURCE,
277
    CS_SYNCTARGET,
278
    CS_PAUSEDSYNCS,
279
    CS_PAUSEDSYNCT,
280
    ])
281

  
282
  DS_DISKLESS = "Diskless"
283
  DS_ATTACHING = "Attaching" # transient state
284
  DS_FAILED = "Failed" # transient state, next: diskless
285
  DS_NEGOTIATING = "Negotiating" # transient state
286
  DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
287
  DS_OUTDATED = "Outdated"
288
  DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
289
  DS_CONSISTENT = "Consistent"
290
  DS_UPTODATE = "UpToDate" # normal state
291

  
292
  RO_PRIMARY = "Primary"
293
  RO_SECONDARY = "Secondary"
294
  RO_UNKNOWN = "Unknown"
295

  
296
  def __init__(self, procline):
297
    u = self.UNCONF_RE.match(procline)
298
    if u:
299
      self.cstatus = self.CS_UNCONFIGURED
300
      self.lrole = self.rrole = self.ldisk = self.rdisk = None
301
    else:
302
      m = self.LINE_RE.match(procline)
303
      if not m:
304
        raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
305
      self.cstatus = m.group(1)
306
      self.lrole = m.group(2)
307
      self.rrole = m.group(3)
308
      self.ldisk = m.group(4)
309
      self.rdisk = m.group(5)
310

  
311
    # end reading of data from the LINE_RE or UNCONF_RE
312

  
313
    self.is_standalone = self.cstatus == self.CS_STANDALONE
314
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
315
    self.is_connected = self.cstatus == self.CS_CONNECTED
316
    self.is_primary = self.lrole == self.RO_PRIMARY
317
    self.is_secondary = self.lrole == self.RO_SECONDARY
318
    self.peer_primary = self.rrole == self.RO_PRIMARY
319
    self.peer_secondary = self.rrole == self.RO_SECONDARY
320
    self.both_primary = self.is_primary and self.peer_primary
321
    self.both_secondary = self.is_secondary and self.peer_secondary
322

  
323
    self.is_diskless = self.ldisk == self.DS_DISKLESS
324
    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
325

  
326
    self.is_in_resync = self.cstatus in self.CSET_SYNC
327
    self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
328

  
329
    m = self.SYNC_RE.match(procline)
330
    if m:
331
      self.sync_percent = float(m.group(1))
332
      hours = int(m.group(2))
333
      minutes = int(m.group(3))
334
      seconds = int(m.group(4))
335
      self.est_time = hours * 3600 + minutes * 60 + seconds
336
    else:
337
      # we have (in this if branch) no percent information, but if
338
      # we're resyncing we need to 'fake' a sync percent information,
339
      # as this is how cmdlib determines if it makes sense to wait for
340
      # resyncing or not
341
      if self.is_in_resync:
342
        self.sync_percent = 0
343
      else:
344
        self.sync_percent = None
345
      self.est_time = None
346

  
347

  
348
class DRBD8(BaseDRBD):
349
  """DRBD v8.x block device.
350

  
351
  This implements the local host part of the DRBD device, i.e. it
352
  doesn't do anything to the supposed peer. If you need a fully
353
  connected DRBD pair, you need to use this class on both hosts.
354

  
355
  The unique_id for the drbd device is a (local_ip, local_port,
356
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
357
  two children: the data device and the meta_device. The meta device
358
  is checked for valid size and is zeroed on create.
359

  
360
  """
361
  _MAX_MINORS = 255
362
  _PARSE_SHOW = None
363

  
364
  # timeout constants
365
  _NET_RECONFIG_TIMEOUT = 60
366

  
367
  # command line options for barriers
368
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
369
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
370
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
371
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
372

  
373
  def __init__(self, unique_id, children, size, params):
374
    if children and children.count(None) > 0:
375
      children = []
376
    if len(children) not in (0, 2):
377
      raise ValueError("Invalid configuration data %s" % str(children))
378
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
379
      raise ValueError("Invalid configuration data %s" % str(unique_id))
380
    (self._lhost, self._lport,
381
     self._rhost, self._rport,
382
     self._aminor, self._secret) = unique_id
383
    if children:
384
      if not _CanReadDevice(children[1].dev_path):
385
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
386
        children = []
387
    super(DRBD8, self).__init__(unique_id, children, size, params)
388
    self.major = self._DRBD_MAJOR
389
    version = self._GetVersion(self._GetProcData())
390
    if version["k_major"] != 8:
391
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
392
                      " usage: kernel is %s.%s, ganeti wants 8.x",
393
                      version["k_major"], version["k_minor"])
394

  
395
    if (self._lhost is not None and self._lhost == self._rhost and
396
        self._lport == self._rport):
397
      raise ValueError("Invalid configuration data, same local/remote %s" %
398
                       (unique_id,))
399
    self.Attach()
400

  
401 386
  @classmethod
402 387
  def _InitMeta(cls, minor, dev_path):
403 388
    """Initialize a meta device.
......
1304 1289
    """
1305 1290
    self.Shutdown()
1306 1291

  
1292
  def Rename(self, new_id):
1293
    """Rename a device.
1294

  
1295
    This is not supported for drbd devices.
1296

  
1297
    """
1298
    raise errors.ProgrammerError("Can't rename a drbd device")
1299

  
1307 1300
  @classmethod
1308 1301
  def Create(cls, unique_id, children, size, params, excl_stor):
1309 1302
    """Create a new DRBD8 device.
b/lib/bootstrap.py
470 470

  
471 471
  if drbd_helper is not None:
472 472
    try:
473
      curr_helper = drbd.BaseDRBD.GetUsermodeHelper()
473
      curr_helper = drbd.DRBD8.GetUsermodeHelper()
474 474
    except errors.BlockDeviceError, err:
475 475
      raise errors.OpPrereqError("Error while checking drbd helper"
476 476
                                 " (specify --no-drbd-storage if you are not"
b/test/py/ganeti.block.bdev_unittest.py
37 37
import testutils
38 38

  
39 39

  
40
class TestBaseDRBD(testutils.GanetiTestCase):
40
class TestDRBD8(testutils.GanetiTestCase):
41 41
  def testGetVersion(self):
42 42
    data = [
43 43
      ["version: 8.0.12 (api:76/proto:86-91)"],
......
71 71
      }
72 72
    ]
73 73
    for d,r in zip(data, result):
74
      self.assertEqual(drbd.BaseDRBD._GetVersion(d), r)
74
      self.assertEqual(drbd.DRBD8._GetVersion(d), r)
75 75

  
76 76

  
77 77
class TestDRBD8Runner(testutils.GanetiTestCase):

Also available in: Unified diff