Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ 2fe690f1

History | View | Annotate | Download (46.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""DRBD block device related functionality"""
23

    
24
import errno
25
import logging
26
import pyparsing as pyp
27
import re
28
import shlex
29
import time
30

    
31
from ganeti import constants
32
from ganeti import utils
33
from ganeti import errors
34
from ganeti import compat
35
from ganeti import netutils
36
from ganeti import objects
37
from ganeti.block import base
38

    
39

    
40
# Size of reads in _CanReadDevice
41

    
42
_DEVICE_READ_SIZE = 128 * 1024
43

    
44

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

48
  Note that this class is meant to be used to parse one of the entries returned
49
  from L{DRBD8Info._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_unconfigured = self.cstatus == self.CS_UNCONFIGURED
123
    self.is_primary = self.lrole == self.RO_PRIMARY
124
    self.is_secondary = self.lrole == self.RO_SECONDARY
125
    self.peer_primary = self.rrole == self.RO_PRIMARY
126
    self.peer_secondary = self.rrole == self.RO_SECONDARY
127
    self.both_primary = self.is_primary and self.peer_primary
128
    self.both_secondary = self.is_secondary and self.peer_secondary
129

    
130
    self.is_diskless = self.ldisk == self.DS_DISKLESS
131
    self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
132

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

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

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

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

275
  The unique_id for the drbd device is a (local_ip, local_port,
276
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
277
  two children: the data device and the meta_device. The meta device
278
  is checked for valid size and is zeroed on create.
279

280
  """
281
  _DRBD_MAJOR = 147
282

    
283
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
284

    
285
  _MAX_MINORS = 255
286
  _PARSE_SHOW = None
287

    
288
  # timeout constants
289
  _NET_RECONFIG_TIMEOUT = 60
290

    
291
  # command line options for barriers
292
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
293
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
294
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
295
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
296

    
297
  def __init__(self, unique_id, children, size, params):
298
    if children and children.count(None) > 0:
299
      children = []
300
    if len(children) not in (0, 2):
301
      raise ValueError("Invalid configuration data %s" % str(children))
302
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
303
      raise ValueError("Invalid configuration data %s" % str(unique_id))
304
    (self._lhost, self._lport,
305
     self._rhost, self._rport,
306
     self._aminor, self._secret) = unique_id
307
    if children:
308
      if not _CanReadDevice(children[1].dev_path):
309
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
310
        children = []
311
    super(DRBD8, self).__init__(unique_id, children, size, params)
312
    self.major = self._DRBD_MAJOR
313

    
314
    drbd_info = DRBD8Info.CreateFromFile()
315
    version = drbd_info.GetVersion()
316
    if version["k_major"] != 8:
317
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
318
                      " usage: kernel is %s.%s, ganeti wants 8.x",
319
                      version["k_major"], version["k_minor"])
320

    
321
    if (self._lhost is not None and self._lhost == self._rhost and
322
            self._lport == self._rport):
323
      raise ValueError("Invalid configuration data, same local/remote %s" %
324
                       (unique_id,))
325
    self.Attach()
326

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

331
    """
332
    try:
333
      helper = utils.ReadFile(filename).splitlines()[0]
334
    except EnvironmentError, err:
335
      if err.errno == errno.ENOENT:
336
        base.ThrowError("The file %s cannot be opened, check if the module"
337
                        " is loaded (%s)", filename, str(err))
338
      else:
339
        base.ThrowError("Can't read DRBD helper file %s: %s",
340
                        filename, str(err))
341
    if not helper:
342
      base.ThrowError("Can't read any data from %s", filename)
343
    return helper
344

    
345
  @staticmethod
346
  def _DevPath(minor):
347
    """Return the path to a drbd device for a given minor.
348

349
    """
350
    return "/dev/drbd%d" % minor
351

    
352
  @classmethod
353
  def GetUsedDevs(cls):
354
    """Compute the list of used DRBD devices.
355

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

    
361
  def _SetFromMinor(self, minor):
362
    """Set our parameters based on the given minor.
363

364
    This sets our minor variable and our dev_path.
365

366
    """
367
    if minor is None:
368
      self.minor = self.dev_path = None
369
      self.attached = False
370
    else:
371
      self.minor = minor
372
      self.dev_path = self._DevPath(minor)
373
      self.attached = True
374

    
375
  @staticmethod
376
  def _CheckMetaSize(meta_device):
377
    """Check if the given meta device looks like a valid one.
378

379
    This currently only checks the size, which must be around
380
    128MiB.
381

382
    """
383
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
384
    if result.failed:
385
      base.ThrowError("Failed to get device size: %s - %s",
386
                      result.fail_reason, result.output)
387
    try:
388
      sectors = int(result.stdout)
389
    except (TypeError, ValueError):
390
      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
391
    num_bytes = sectors * 512
392
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
393
      base.ThrowError("Meta device too small (%.2fMib)",
394
                      (num_bytes / 1024 / 1024))
395
    # the maximum *valid* size of the meta device when living on top
396
    # of LVM is hard to compute: it depends on the number of stripes
397
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
398
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
399
    # size meta device; as such, we restrict it to 1GB (a little bit
400
    # too generous, but making assumptions about PE size is hard)
401
    if num_bytes > 1024 * 1024 * 1024:
402
      base.ThrowError("Meta device too big (%.2fMiB)",
403
                      (num_bytes / 1024 / 1024))
404

    
405
  @classmethod
406
  def _InitMeta(cls, minor, dev_path):
407
    """Initialize a meta device.
408

409
    This will not work if the given minor is in use.
410

411
    """
412
    # Zero the metadata first, in order to make sure drbdmeta doesn't
413
    # try to auto-detect existing filesystems or similar (see
414
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
415
    # care about the first 128MB of data in the device, even though it
416
    # can be bigger
417
    result = utils.RunCmd([constants.DD_CMD,
418
                           "if=/dev/zero", "of=%s" % dev_path,
419
                           "bs=1048576", "count=128", "oflag=direct"])
420
    if result.failed:
421
      base.ThrowError("Can't wipe the meta device: %s", result.output)
422

    
423
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
424
                           "v08", dev_path, "0", "create-md"])
425
    if result.failed:
426
      base.ThrowError("Can't initialize meta device: %s", result.output)
427

    
428
  def _FindUnusedMinor(self):
429
    """Find an unused DRBD device.
430

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

434
    """
435

    
436
    highest = None
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

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

    
450
    return highest + 1
451

    
452
  @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
  def _GetShowData(cls, minor):
512
    """Return the `drbdsetup show` data for a minor.
513

514
    """
515
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
516
    if result.failed:
517
      logging.error("Can't display the drbd config: %s - %s",
518
                    result.fail_reason, result.output)
519
      return None
520
    return result.stdout
521

    
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
  def _MatchesLocal(self, info):
563
    """Test if our local config matches with an existing device.
564

565
    The parameter should be as returned from `_GetDevInfo()`. This
566
    method tests if our local backing device is the same as the one in
567
    the info parameter, in effect testing if we look like the given
568
    device.
569

570
    """
571
    if self._children:
572
      backend, meta = self._children
573
    else:
574
      backend = meta = None
575

    
576
    if backend is not None:
577
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
578
    else:
579
      retval = ("local_dev" not in info)
580

    
581
    if meta is not None:
582
      retval = retval and ("meta_dev" in info and
583
                           info["meta_dev"] == meta.dev_path)
584
      retval = retval and ("meta_index" in info and
585
                           info["meta_index"] == 0)
586
    else:
587
      retval = retval and ("meta_dev" not in info and
588
                           "meta_index" not in info)
589
    return retval
590

    
591
  def _MatchesNet(self, info):
592
    """Test if our network config matches with an existing device.
593

594
    The parameter should be as returned from `_GetDevInfo()`. This
595
    method tests if our network configuration is the same as the one
596
    in the info parameter, in effect testing if we look like the given
597
    device.
598

599
    """
600
    if (((self._lhost is None and not ("local_addr" in info)) and
601
         (self._rhost is None and not ("remote_addr" in info)))):
602
      return True
603

    
604
    if self._lhost is None:
605
      return False
606

    
607
    if not ("local_addr" in info and
608
            "remote_addr" in info):
609
      return False
610

    
611
    retval = (info["local_addr"] == (self._lhost, self._lport))
612
    retval = (retval and
613
              info["remote_addr"] == (self._rhost, self._rport))
614
    return retval
615

    
616
  def _AssembleLocal(self, minor, backend, meta, size):
617
    """Configure the local part of a DRBD device.
618

619
    """
620
    args = ["drbdsetup", self._DevPath(minor), "disk",
621
            backend, meta, "0",
622
            "-e", "detach",
623
            "--create-device"]
624
    if size:
625
      args.extend(["-d", "%sm" % size])
626

    
627
    drbd_info = DRBD8Info.CreateFromFile()
628
    version = drbd_info.GetVersion()
629
    vmaj = version["k_major"]
630
    vmin = version["k_minor"]
631
    vrel = version["k_point"]
632

    
633
    barrier_args = \
634
      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
635
                                   self.params[constants.LDP_BARRIERS],
636
                                   self.params[constants.LDP_NO_META_FLUSH])
637
    args.extend(barrier_args)
638

    
639
    if self.params[constants.LDP_DISK_CUSTOM]:
640
      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
641

    
642
    result = utils.RunCmd(args)
643
    if result.failed:
644
      base.ThrowError("drbd%d: can't attach local disk: %s",
645
                      minor, result.output)
646

    
647
  @classmethod
648
  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
649
                              disable_meta_flush):
650
    """Compute the DRBD command line parameters for disk barriers
651

652
    Returns a list of the disk barrier parameters as requested via the
653
    disabled_barriers and disable_meta_flush arguments, and according to the
654
    supported ones in the DRBD version vmaj.vmin.vrel
655

656
    If the desired option is unsupported, raises errors.BlockDeviceError.
657

658
    """
659
    disabled_barriers_set = frozenset(disabled_barriers)
660
    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
661
      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
662
                                    " barriers" % disabled_barriers)
663

    
664
    args = []
665

    
666
    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
667
    # does not exist)
668
    if not vmaj == 8 and vmin in (0, 2, 3):
669
      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
670
                                    (vmaj, vmin, vrel))
671

    
672
    def _AppendOrRaise(option, min_version):
673
      """Helper for DRBD options"""
674
      if min_version is not None and vrel >= min_version:
675
        args.append(option)
676
      else:
677
        raise errors.BlockDeviceError("Could not use the option %s as the"
678
                                      " DRBD version %d.%d.%d does not support"
679
                                      " it." % (option, vmaj, vmin, vrel))
680

    
681
    # the minimum version for each feature is encoded via pairs of (minor
682
    # version -> x) where x is version in which support for the option was
683
    # introduced.
684
    meta_flush_supported = disk_flush_supported = {
685
      0: 12,
686
      2: 7,
687
      3: 0,
688
      }
689

    
690
    disk_drain_supported = {
691
      2: 7,
692
      3: 0,
693
      }
694

    
695
    disk_barriers_supported = {
696
      3: 0,
697
      }
698

    
699
    # meta flushes
700
    if disable_meta_flush:
701
      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
702
                     meta_flush_supported.get(vmin, None))
703

    
704
    # disk flushes
705
    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
706
      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
707
                     disk_flush_supported.get(vmin, None))
708

    
709
    # disk drain
710
    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
711
      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
712
                     disk_drain_supported.get(vmin, None))
713

    
714
    # disk barriers
715
    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
716
      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
717
                     disk_barriers_supported.get(vmin, None))
718

    
719
    return args
720

    
721
  def _AssembleNet(self, minor, net_info, protocol,
722
                   dual_pri=False, hmac=None, secret=None):
723
    """Configure the network part of the device.
724

725
    """
726
    lhost, lport, rhost, rport = net_info
727
    if None in net_info:
728
      # we don't want network connection and actually want to make
729
      # sure its shutdown
730
      self._ShutdownNet(minor)
731
      return
732

    
733
    # Workaround for a race condition. When DRBD is doing its dance to
734
    # establish a connection with its peer, it also sends the
735
    # synchronization speed over the wire. In some cases setting the
736
    # sync speed only after setting up both sides can race with DRBD
737
    # connecting, hence we set it here before telling DRBD anything
738
    # about its peer.
739
    sync_errors = self._SetMinorSyncParams(minor, self.params)
740
    if sync_errors:
741
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
742
                      (minor, utils.CommaJoin(sync_errors)))
743

    
744
    if netutils.IP6Address.IsValid(lhost):
745
      if not netutils.IP6Address.IsValid(rhost):
746
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
747
                        (minor, lhost, rhost))
748
      family = "ipv6"
749
    elif netutils.IP4Address.IsValid(lhost):
750
      if not netutils.IP4Address.IsValid(rhost):
751
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
752
                        (minor, lhost, rhost))
753
      family = "ipv4"
754
    else:
755
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
756

    
757
    args = ["drbdsetup", self._DevPath(minor), "net",
758
            "%s:%s:%s" % (family, lhost, lport),
759
            "%s:%s:%s" % (family, rhost, rport), protocol,
760
            "-A", "discard-zero-changes",
761
            "-B", "consensus",
762
            "--create-device",
763
            ]
764
    if dual_pri:
765
      args.append("-m")
766
    if hmac and secret:
767
      args.extend(["-a", hmac, "-x", secret])
768

    
769
    if self.params[constants.LDP_NET_CUSTOM]:
770
      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
771

    
772
    result = utils.RunCmd(args)
773
    if result.failed:
774
      base.ThrowError("drbd%d: can't setup network: %s - %s",
775
                      minor, result.fail_reason, result.output)
776

    
777
    def _CheckNetworkConfig():
778
      info = self._GetDevInfo(self._GetShowData(minor))
779
      if not "local_addr" in info or not "remote_addr" in info:
780
        raise utils.RetryAgain()
781

    
782
      if (info["local_addr"] != (lhost, lport) or
783
          info["remote_addr"] != (rhost, rport)):
784
        raise utils.RetryAgain()
785

    
786
    try:
787
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
788
    except utils.RetryTimeout:
789
      base.ThrowError("drbd%d: timeout while configuring network", minor)
790

    
791
  def AddChildren(self, devices):
792
    """Add a disk to the DRBD device.
793

794
    """
795
    if self.minor is None:
796
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
797
                      self._aminor)
798
    if len(devices) != 2:
799
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
800
    info = self._GetDevInfo(self._GetShowData(self.minor))
801
    if "local_dev" in info:
802
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
803
    backend, meta = devices
804
    if backend.dev_path is None or meta.dev_path is None:
805
      base.ThrowError("drbd%d: children not ready during AddChildren",
806
                      self.minor)
807
    backend.Open()
808
    meta.Open()
809
    self._CheckMetaSize(meta.dev_path)
810
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
811

    
812
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
813
    self._children = devices
814

    
815
  def RemoveChildren(self, devices):
816
    """Detach the drbd device from local storage.
817

818
    """
819
    if self.minor is None:
820
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
821
                      self._aminor)
822
    # early return if we don't actually have backing storage
823
    info = self._GetDevInfo(self._GetShowData(self.minor))
824
    if "local_dev" not in info:
825
      return
826
    if len(self._children) != 2:
827
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
828
                      self._children)
829
    if self._children.count(None) == 2: # we don't actually have children :)
830
      logging.warning("drbd%d: requested detach while detached", self.minor)
831
      return
832
    if len(devices) != 2:
833
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
834
                      self.minor)
835
    for child, dev in zip(self._children, devices):
836
      if dev != child.dev_path:
837
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
838
                        " RemoveChildren", self.minor, dev, child.dev_path)
839

    
840
    self._ShutdownLocal(self.minor)
841
    self._children = []
842

    
843
  def _SetMinorSyncParams(self, minor, params):
844
    """Set the parameters of the DRBD syncer.
845

846
    This is the low-level implementation.
847

848
    @type minor: int
849
    @param minor: the drbd minor whose settings we change
850
    @type params: dict
851
    @param params: LD level disk parameters related to the synchronization
852
    @rtype: list
853
    @return: a list of error messages
854

855
    """
856

    
857
    args = ["drbdsetup", self._DevPath(minor), "syncer"]
858
    if params[constants.LDP_DYNAMIC_RESYNC]:
859
      drbd_info = DRBD8Info.CreateFromFile()
860
      version = drbd_info.GetVersion()
861
      vmin = version["k_minor"]
862
      vrel = version["k_point"]
863

    
864
      # By definition we are using 8.x, so just check the rest of the version
865
      # number
866
      if vmin != 3 or vrel < 9:
867
        msg = ("The current DRBD version (8.%d.%d) does not support the "
868
               "dynamic resync speed controller" % (vmin, vrel))
869
        logging.error(msg)
870
        return [msg]
871

    
872
      if params[constants.LDP_PLAN_AHEAD] == 0:
873
        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
874
               " controller at DRBD level. If you want to disable it, please"
875
               " set the dynamic-resync disk parameter to False.")
876
        logging.error(msg)
877
        return [msg]
878

    
879
      # add the c-* parameters to args
880
      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
881
                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
882
                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
883
                   "--c-max-rate", params[constants.LDP_MAX_RATE],
884
                   "--c-min-rate", params[constants.LDP_MIN_RATE],
885
                   ])
886

    
887
    else:
888
      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
889

    
890
    args.append("--create-device")
891
    result = utils.RunCmd(args)
892
    if result.failed:
893
      msg = ("Can't change syncer rate: %s - %s" %
894
             (result.fail_reason, result.output))
895
      logging.error(msg)
896
      return [msg]
897

    
898
    return []
899

    
900
  def SetSyncParams(self, params):
901
    """Set the synchronization parameters of the DRBD syncer.
902

903
    @type params: dict
904
    @param params: LD level disk parameters related to the synchronization
905
    @rtype: list
906
    @return: a list of error messages, emitted both by the current node and by
907
    children. An empty list means no errors
908

909
    """
910
    if self.minor is None:
911
      err = "Not attached during SetSyncParams"
912
      logging.info(err)
913
      return [err]
914

    
915
    children_result = super(DRBD8, self).SetSyncParams(params)
916
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
917
    return children_result
918

    
919
  def PauseResumeSync(self, pause):
920
    """Pauses or resumes the sync of a DRBD device.
921

922
    @param pause: Wether to pause or resume
923
    @return: the success of the operation
924

925
    """
926
    if self.minor is None:
927
      logging.info("Not attached during PauseSync")
928
      return False
929

    
930
    children_result = super(DRBD8, self).PauseResumeSync(pause)
931

    
932
    if pause:
933
      cmd = "pause-sync"
934
    else:
935
      cmd = "resume-sync"
936

    
937
    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
938
    if result.failed:
939
      logging.error("Can't %s: %s - %s", cmd,
940
                    result.fail_reason, result.output)
941
    return not result.failed and children_result
942

    
943
  def GetProcStatus(self):
944
    """Return device data from /proc.
945

946
    """
947
    if self.minor is None:
948
      base.ThrowError("drbd%d: GetStats() called while not attached",
949
                      self._aminor)
950
    drbd_info = DRBD8Info.CreateFromFile()
951
    if not drbd_info.HasMinorStatus(self.minor):
952
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
953
    return drbd_info.GetMinorStatus(self.minor)
954

    
955
  def GetSyncStatus(self):
956
    """Returns the sync status of the device.
957

958

959
    If sync_percent is None, it means all is ok
960
    If estimated_time is None, it means we can't estimate
961
    the time needed, otherwise it's the time left in seconds.
962

963

964
    We set the is_degraded parameter to True on two conditions:
965
    network not connected or local disk missing.
966

967
    We compute the ldisk parameter based on whether we have a local
968
    disk or not.
969

970
    @rtype: objects.BlockDevStatus
971

972
    """
973
    if self.minor is None and not self.Attach():
974
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
975

    
976
    stats = self.GetProcStatus()
977
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
978

    
979
    if stats.is_disk_uptodate:
980
      ldisk_status = constants.LDS_OKAY
981
    elif stats.is_diskless:
982
      ldisk_status = constants.LDS_FAULTY
983
    else:
984
      ldisk_status = constants.LDS_UNKNOWN
985

    
986
    return objects.BlockDevStatus(dev_path=self.dev_path,
987
                                  major=self.major,
988
                                  minor=self.minor,
989
                                  sync_percent=stats.sync_percent,
990
                                  estimated_time=stats.est_time,
991
                                  is_degraded=is_degraded,
992
                                  ldisk_status=ldisk_status)
993

    
994
  def Open(self, force=False):
995
    """Make the local state primary.
996

997
    If the 'force' parameter is given, the '-o' option is passed to
998
    drbdsetup. Since this is a potentially dangerous operation, the
999
    force flag should be only given after creation, when it actually
1000
    is mandatory.
1001

1002
    """
1003
    if self.minor is None and not self.Attach():
1004
      logging.error("DRBD cannot attach to a device during open")
1005
      return False
1006
    cmd = ["drbdsetup", self.dev_path, "primary"]
1007
    if force:
1008
      cmd.append("-o")
1009
    result = utils.RunCmd(cmd)
1010
    if result.failed:
1011
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1012
                      result.output)
1013

    
1014
  def Close(self):
1015
    """Make the local state secondary.
1016

1017
    This will, of course, fail if the device is in use.
1018

1019
    """
1020
    if self.minor is None and not self.Attach():
1021
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1022
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1023
    if result.failed:
1024
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1025
                      self.minor, result.output)
1026

    
1027
  def DisconnectNet(self):
1028
    """Removes network configuration.
1029

1030
    This method shutdowns the network side of the device.
1031

1032
    The method will wait up to a hardcoded timeout for the device to
1033
    go into standalone after the 'disconnect' command before
1034
    re-configuring it, as sometimes it takes a while for the
1035
    disconnect to actually propagate and thus we might issue a 'net'
1036
    command while the device is still connected. If the device will
1037
    still be attached to the network and we time out, we raise an
1038
    exception.
1039

1040
    """
1041
    if self.minor is None:
1042
      base.ThrowError("drbd%d: disk not attached in re-attach net",
1043
                      self._aminor)
1044

    
1045
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1046
      base.ThrowError("drbd%d: DRBD disk missing network info in"
1047
                      " DisconnectNet()", self.minor)
1048

    
1049
    class _DisconnectStatus:
1050
      def __init__(self, ever_disconnected):
1051
        self.ever_disconnected = ever_disconnected
1052

    
1053
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
1054

    
1055
    def _WaitForDisconnect():
1056
      if self.GetProcStatus().is_standalone:
1057
        return
1058

    
1059
      # retry the disconnect, it seems possible that due to a well-time
1060
      # disconnect on the peer, my disconnect command might be ignored and
1061
      # forgotten
1062
      dstatus.ever_disconnected = \
1063
        base.IgnoreError(self._ShutdownNet, self.minor) or \
1064
        dstatus.ever_disconnected
1065

    
1066
      raise utils.RetryAgain()
1067

    
1068
    # Keep start time
1069
    start_time = time.time()
1070

    
1071
    try:
1072
      # Start delay at 100 milliseconds and grow up to 2 seconds
1073
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1074
                  self._NET_RECONFIG_TIMEOUT)
1075
    except utils.RetryTimeout:
1076
      if dstatus.ever_disconnected:
1077
        msg = ("drbd%d: device did not react to the"
1078
               " 'disconnect' command in a timely manner")
1079
      else:
1080
        msg = "drbd%d: can't shutdown network, even after multiple retries"
1081

    
1082
      base.ThrowError(msg, self.minor)
1083

    
1084
    reconfig_time = time.time() - start_time
1085
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1086
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1087
                   self.minor, reconfig_time)
1088

    
1089
  def AttachNet(self, multimaster):
1090
    """Reconnects the network.
1091

1092
    This method connects the network side of the device with a
1093
    specified multi-master flag. The device needs to be 'Standalone'
1094
    but have valid network configuration data.
1095

1096
    Args:
1097
      - multimaster: init the network in dual-primary mode
1098

1099
    """
1100
    if self.minor is None:
1101
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1102

    
1103
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1104
      base.ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1105

    
1106
    status = self.GetProcStatus()
1107

    
1108
    if not status.is_standalone:
1109
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
1110
                      self.minor)
1111

    
1112
    self._AssembleNet(self.minor,
1113
                      (self._lhost, self._lport, self._rhost, self._rport),
1114
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1115
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1116

    
1117
  def Attach(self):
1118
    """Check if our minor is configured.
1119

1120
    This doesn't do any device configurations - it only checks if the
1121
    minor is in a state different from Unconfigured.
1122

1123
    Note that this function will not change the state of the system in
1124
    any way (except in case of side-effects caused by reading from
1125
    /proc).
1126

1127
    """
1128
    used_devs = self.GetUsedDevs()
1129
    if self._aminor in used_devs:
1130
      minor = self._aminor
1131
    else:
1132
      minor = None
1133

    
1134
    self._SetFromMinor(minor)
1135
    return minor is not None
1136

    
1137
  def Assemble(self):
1138
    """Assemble the drbd.
1139

1140
    Method:
1141
      - if we have a configured device, we try to ensure that it matches
1142
        our config
1143
      - if not, we create it from zero
1144
      - anyway, set the device parameters
1145

1146
    """
1147
    super(DRBD8, self).Assemble()
1148

    
1149
    self.Attach()
1150
    if self.minor is None:
1151
      # local device completely unconfigured
1152
      self._FastAssemble()
1153
    else:
1154
      # we have to recheck the local and network status and try to fix
1155
      # the device
1156
      self._SlowAssemble()
1157

    
1158
    sync_errors = self.SetSyncParams(self.params)
1159
    if sync_errors:
1160
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1161
                      (self.minor, utils.CommaJoin(sync_errors)))
1162

    
1163
  def _SlowAssemble(self):
1164
    """Assembles the DRBD device from a (partially) configured device.
1165

1166
    In case of partially attached (local device matches but no network
1167
    setup), we perform the network attach. If successful, we re-test
1168
    the attach if can return success.
1169

1170
    """
1171
    # TODO: Rewrite to not use a for loop just because there is 'break'
1172
    # pylint: disable=W0631
1173
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
1174
    for minor in (self._aminor,):
1175
      info = self._GetDevInfo(self._GetShowData(minor))
1176
      match_l = self._MatchesLocal(info)
1177
      match_r = self._MatchesNet(info)
1178

    
1179
      if match_l and match_r:
1180
        # everything matches
1181
        break
1182

    
1183
      if match_l and not match_r and "local_addr" not in info:
1184
        # disk matches, but not attached to network, attach and recheck
1185
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1186
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1187
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1188
          break
1189
        else:
1190
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1191
                          " show' disagrees", minor)
1192

    
1193
      if match_r and "local_dev" not in info:
1194
        # no local disk, but network attached and it matches
1195
        self._AssembleLocal(minor, self._children[0].dev_path,
1196
                            self._children[1].dev_path, self.size)
1197
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1198
          break
1199
        else:
1200
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1201
                          " show' disagrees", minor)
1202

    
1203
      # this case must be considered only if we actually have local
1204
      # storage, i.e. not in diskless mode, because all diskless
1205
      # devices are equal from the point of view of local
1206
      # configuration
1207
      if (match_l and "local_dev" in info and
1208
          not match_r and "local_addr" in info):
1209
        # strange case - the device network part points to somewhere
1210
        # else, even though its local storage is ours; as we own the
1211
        # drbd space, we try to disconnect from the remote peer and
1212
        # reconnect to our correct one
1213
        try:
1214
          self._ShutdownNet(minor)
1215
        except errors.BlockDeviceError, err:
1216
          base.ThrowError("drbd%d: device has correct local storage, wrong"
1217
                          " remote peer and is unable to disconnect in order"
1218
                          " to attach to the correct peer: %s", minor, str(err))
1219
        # note: _AssembleNet also handles the case when we don't want
1220
        # local storage (i.e. one or more of the _[lr](host|port) is
1221
        # None)
1222
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1223
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1224
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1225
          break
1226
        else:
1227
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1228
                          " show' disagrees", minor)
1229

    
1230
    else:
1231
      minor = None
1232

    
1233
    self._SetFromMinor(minor)
1234
    if minor is None:
1235
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1236
                      self._aminor)
1237

    
1238
  def _FastAssemble(self):
1239
    """Assemble the drbd device from zero.
1240

1241
    This is run when in Assemble we detect our minor is unused.
1242

1243
    """
1244
    minor = self._aminor
1245
    if self._children and self._children[0] and self._children[1]:
1246
      self._AssembleLocal(minor, self._children[0].dev_path,
1247
                          self._children[1].dev_path, self.size)
1248
    if self._lhost and self._lport and self._rhost and self._rport:
1249
      self._AssembleNet(minor,
1250
                        (self._lhost, self._lport, self._rhost, self._rport),
1251
                        constants.DRBD_NET_PROTOCOL,
1252
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1253
    self._SetFromMinor(minor)
1254

    
1255
  @classmethod
1256
  def _ShutdownLocal(cls, minor):
1257
    """Detach from the local device.
1258

1259
    I/Os will continue to be served from the remote device. If we
1260
    don't have a remote device, this operation will fail.
1261

1262
    """
1263
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1264
    if result.failed:
1265
      base.ThrowError("drbd%d: can't detach local disk: %s",
1266
                      minor, result.output)
1267

    
1268
  @classmethod
1269
  def _ShutdownNet(cls, minor):
1270
    """Disconnect from the remote peer.
1271

1272
    This fails if we don't have a local device.
1273

1274
    """
1275
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1276
    if result.failed:
1277
      base.ThrowError("drbd%d: can't shutdown network: %s",
1278
                      minor, result.output)
1279

    
1280
  @classmethod
1281
  def _ShutdownAll(cls, minor):
1282
    """Deactivate the device.
1283

1284
    This will, of course, fail if the device is in use.
1285

1286
    """
1287
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1288
    if result.failed:
1289
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
1290
                      minor, result.output)
1291

    
1292
  def Shutdown(self):
1293
    """Shutdown the DRBD device.
1294

1295
    """
1296
    if self.minor is None and not self.Attach():
1297
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1298
      return
1299
    minor = self.minor
1300
    self.minor = None
1301
    self.dev_path = None
1302
    self._ShutdownAll(minor)
1303

    
1304
  def Remove(self):
1305
    """Stub remove for DRBD devices.
1306

1307
    """
1308
    self.Shutdown()
1309

    
1310
  def Rename(self, new_id):
1311
    """Rename a device.
1312

1313
    This is not supported for drbd devices.
1314

1315
    """
1316
    raise errors.ProgrammerError("Can't rename a drbd device")
1317

    
1318
  @classmethod
1319
  def Create(cls, unique_id, children, size, params, excl_stor):
1320
    """Create a new DRBD8 device.
1321

1322
    Since DRBD devices are not created per se, just assembled, this
1323
    function only initializes the metadata.
1324

1325
    """
1326
    if len(children) != 2:
1327
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1328
    if excl_stor:
1329
      raise errors.ProgrammerError("DRBD device requested with"
1330
                                   " exclusive_storage")
1331
    # check that the minor is unused
1332
    aminor = unique_id[4]
1333

    
1334
    drbd_info = DRBD8Info.CreateFromFile()
1335
    if drbd_info.HasMinorStatus(aminor):
1336
      status = drbd_info.GetMinorStatus(aminor)
1337
      in_use = status.is_in_use
1338
    else:
1339
      in_use = False
1340
    if in_use:
1341
      base.ThrowError("drbd%d: minor is already in use at Create() time",
1342
                      aminor)
1343
    meta = children[1]
1344
    meta.Assemble()
1345
    if not meta.Attach():
1346
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
1347
                      aminor, meta)
1348
    cls._CheckMetaSize(meta.dev_path)
1349
    cls._InitMeta(aminor, meta.dev_path)
1350
    return cls(unique_id, children, size, params)
1351

    
1352
  def Grow(self, amount, dryrun, backingstore):
1353
    """Resize the DRBD device and its backing storage.
1354

1355
    """
1356
    if self.minor is None:
1357
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
1358
    if len(self._children) != 2 or None in self._children:
1359
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
1360
    self._children[0].Grow(amount, dryrun, backingstore)
1361
    if dryrun or backingstore:
1362
      # DRBD does not support dry-run mode and is not backing storage,
1363
      # so we'll return here
1364
      return
1365
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1366
                           "%dm" % (self.size + amount)])
1367
    if result.failed:
1368
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1369

    
1370

    
1371
def _CanReadDevice(path):
1372
  """Check if we can read from the given device.
1373

1374
  This tries to read the first 128k of the device.
1375

1376
  """
1377
  try:
1378
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1379
    return True
1380
  except EnvironmentError:
1381
    logging.warning("Can't read from device %s", path, exc_info=True)
1382
    return False