Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ fd300bc7

History | View | Annotate | Download (45.6 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):
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{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.
165

166
  """
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
  _DRBD_MAJOR = 147
173
  _ST_UNCONFIGURED = DRBD8Status.CS_UNCONFIGURED
174
  _ST_WFCONNECTION = DRBD8Status.CS_WFCONNECTION
175
  _ST_CONNECTED = DRBD8Status.CS_CONNECTED
176

    
177
  _STATUS_FILE = constants.DRBD_STATUS_FILE
178
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
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

    
220
  @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
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
299
    """Returns DRBD usermode_helper currently set.
300

301
    """
302
    try:
303
      helper = utils.ReadFile(filename).splitlines()[0]
304
    except EnvironmentError, err:
305
      if err.errno == errno.ENOENT:
306
        base.ThrowError("The file %s cannot be opened, check if the module"
307
                        " is loaded (%s)", filename, str(err))
308
      else:
309
        base.ThrowError("Can't read DRBD helper file %s: %s",
310
                        filename, str(err))
311
    if not helper:
312
      base.ThrowError("Can't read any data from %s", filename)
313
    return helper
314

    
315
  @staticmethod
316
  def _DevPath(minor):
317
    """Return the path to a drbd device for a given minor.
318

319
    """
320
    return "/dev/drbd%d" % minor
321

    
322
  @classmethod
323
  def GetUsedDevs(cls):
324
    """Compute the list of used DRBD devices.
325

326
    """
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
341

    
342
  def _SetFromMinor(self, minor):
343
    """Set our parameters based on the given minor.
344

345
    This sets our minor variable and our dev_path.
346

347
    """
348
    if minor is None:
349
      self.minor = self.dev_path = None
350
      self.attached = False
351
    else:
352
      self.minor = minor
353
      self.dev_path = self._DevPath(minor)
354
      self.attached = True
355

    
356
  @staticmethod
357
  def _CheckMetaSize(meta_device):
358
    """Check if the given meta device looks like a valid one.
359

360
    This currently only checks the size, which must be around
361
    128MiB.
362

363
    """
364
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
365
    if result.failed:
366
      base.ThrowError("Failed to get device size: %s - %s",
367
                      result.fail_reason, result.output)
368
    try:
369
      sectors = int(result.stdout)
370
    except (TypeError, ValueError):
371
      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
372
    num_bytes = sectors * 512
373
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
374
      base.ThrowError("Meta device too small (%.2fMib)",
375
                      (num_bytes / 1024 / 1024))
376
    # the maximum *valid* size of the meta device when living on top
377
    # of LVM is hard to compute: it depends on the number of stripes
378
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
379
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
380
    # size meta device; as such, we restrict it to 1GB (a little bit
381
    # too generous, but making assumptions about PE size is hard)
382
    if num_bytes > 1024 * 1024 * 1024:
383
      base.ThrowError("Meta device too big (%.2fMiB)",
384
                      (num_bytes / 1024 / 1024))
385

    
386
  @classmethod
387
  def _InitMeta(cls, minor, dev_path):
388
    """Initialize a meta device.
389

390
    This will not work if the given minor is in use.
391

392
    """
393
    # Zero the metadata first, in order to make sure drbdmeta doesn't
394
    # try to auto-detect existing filesystems or similar (see
395
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
396
    # care about the first 128MB of data in the device, even though it
397
    # can be bigger
398
    result = utils.RunCmd([constants.DD_CMD,
399
                           "if=/dev/zero", "of=%s" % dev_path,
400
                           "bs=1048576", "count=128", "oflag=direct"])
401
    if result.failed:
402
      base.ThrowError("Can't wipe the meta device: %s", result.output)
403

    
404
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
405
                           "v08", dev_path, "0", "create-md"])
406
    if result.failed:
407
      base.ThrowError("Can't initialize meta device: %s", result.output)
408

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

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

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

    
419
    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)
428
    if highest is None: # there are no minors in use at all
429
      return 0
430
    if highest >= cls._MAX_MINORS:
431
      logging.error("Error: no free drbd minors!")
432
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
433
    return highest + 1
434

    
435
  @classmethod
436
  def _GetShowParser(cls):
437
    """Return a parser for `drbd show` output.
438

439
    This will either create or return an already-created parser for the
440
    output of the command `drbd show`.
441

442
    """
443
    if cls._PARSE_SHOW is not None:
444
      return cls._PARSE_SHOW
445

    
446
    # pyparsing setup
447
    lbrace = pyp.Literal("{").suppress()
448
    rbrace = pyp.Literal("}").suppress()
449
    lbracket = pyp.Literal("[").suppress()
450
    rbracket = pyp.Literal("]").suppress()
451
    semi = pyp.Literal(";").suppress()
452
    colon = pyp.Literal(":").suppress()
453
    # this also converts the value to an int
454
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
455

    
456
    comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
457
    defa = pyp.Literal("_is_default").suppress()
458
    dbl_quote = pyp.Literal('"').suppress()
459

    
460
    keyword = pyp.Word(pyp.alphanums + "-")
461

    
462
    # value types
463
    value = pyp.Word(pyp.alphanums + "_-/.:")
464
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
465
    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
466
                 pyp.Word(pyp.nums + ".") + colon + number)
467
    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
468
                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
469
                 pyp.Optional(rbracket) + colon + number)
470
    # meta device, extended syntax
471
    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
472
    # device name, extended syntax
473
    device_value = pyp.Literal("minor").suppress() + number
474

    
475
    # a statement
476
    stmt = (~rbrace + keyword + ~lbrace +
477
            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
478
                         device_value) +
479
            pyp.Optional(defa) + semi +
480
            pyp.Optional(pyp.restOfLine).suppress())
481

    
482
    # an entire section
483
    section_name = pyp.Word(pyp.alphas + "_")
484
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
485

    
486
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
487
    bnf.ignore(comment)
488

    
489
    cls._PARSE_SHOW = bnf
490

    
491
    return bnf
492

    
493
  @classmethod
494
  def _GetShowData(cls, minor):
495
    """Return the `drbdsetup show` data for a minor.
496

497
    """
498
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
499
    if result.failed:
500
      logging.error("Can't display the drbd config: %s - %s",
501
                    result.fail_reason, result.output)
502
      return None
503
    return result.stdout
504

    
505
  @classmethod
506
  def _GetDevInfo(cls, out):
507
    """Parse details about a given DRBD minor.
508

509
    This return, if available, the local backing device (as a path)
510
    and the local and remote (ip, port) information from a string
511
    containing the output of the `drbdsetup show` command as returned
512
    by _GetShowData.
513

514
    """
515
    data = {}
516
    if not out:
517
      return data
518

    
519
    bnf = cls._GetShowParser()
520
    # run pyparse
521

    
522
    try:
523
      results = bnf.parseString(out)
524
    except pyp.ParseException, err:
525
      base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
526

    
527
    # and massage the results into our desired format
528
    for section in results:
529
      sname = section[0]
530
      if sname == "_this_host":
531
        for lst in section[1:]:
532
          if lst[0] == "disk":
533
            data["local_dev"] = lst[1]
534
          elif lst[0] == "meta-disk":
535
            data["meta_dev"] = lst[1]
536
            data["meta_index"] = lst[2]
537
          elif lst[0] == "address":
538
            data["local_addr"] = tuple(lst[1:])
539
      elif sname == "_remote_host":
540
        for lst in section[1:]:
541
          if lst[0] == "address":
542
            data["remote_addr"] = tuple(lst[1:])
543
    return data
544

    
545
  def _MatchesLocal(self, info):
546
    """Test if our local config matches with an existing device.
547

548
    The parameter should be as returned from `_GetDevInfo()`. This
549
    method tests if our local backing device is the same as the one in
550
    the info parameter, in effect testing if we look like the given
551
    device.
552

553
    """
554
    if self._children:
555
      backend, meta = self._children
556
    else:
557
      backend = meta = None
558

    
559
    if backend is not None:
560
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
561
    else:
562
      retval = ("local_dev" not in info)
563

    
564
    if meta is not None:
565
      retval = retval and ("meta_dev" in info and
566
                           info["meta_dev"] == meta.dev_path)
567
      retval = retval and ("meta_index" in info and
568
                           info["meta_index"] == 0)
569
    else:
570
      retval = retval and ("meta_dev" not in info and
571
                           "meta_index" not in info)
572
    return retval
573

    
574
  def _MatchesNet(self, info):
575
    """Test if our network config matches with an existing device.
576

577
    The parameter should be as returned from `_GetDevInfo()`. This
578
    method tests if our network configuration is the same as the one
579
    in the info parameter, in effect testing if we look like the given
580
    device.
581

582
    """
583
    if (((self._lhost is None and not ("local_addr" in info)) and
584
         (self._rhost is None and not ("remote_addr" in info)))):
585
      return True
586

    
587
    if self._lhost is None:
588
      return False
589

    
590
    if not ("local_addr" in info and
591
            "remote_addr" in info):
592
      return False
593

    
594
    retval = (info["local_addr"] == (self._lhost, self._lport))
595
    retval = (retval and
596
              info["remote_addr"] == (self._rhost, self._rport))
597
    return retval
598

    
599
  def _AssembleLocal(self, minor, backend, meta, size):
600
    """Configure the local part of a DRBD device.
601

602
    """
603
    args = ["drbdsetup", self._DevPath(minor), "disk",
604
            backend, meta, "0",
605
            "-e", "detach",
606
            "--create-device"]
607
    if size:
608
      args.extend(["-d", "%sm" % size])
609

    
610
    version = self._GetVersion(self._GetProcData())
611
    vmaj = version["k_major"]
612
    vmin = version["k_minor"]
613
    vrel = version["k_point"]
614

    
615
    barrier_args = \
616
      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
617
                                   self.params[constants.LDP_BARRIERS],
618
                                   self.params[constants.LDP_NO_META_FLUSH])
619
    args.extend(barrier_args)
620

    
621
    if self.params[constants.LDP_DISK_CUSTOM]:
622
      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
623

    
624
    result = utils.RunCmd(args)
625
    if result.failed:
626
      base.ThrowError("drbd%d: can't attach local disk: %s",
627
                      minor, result.output)
628

    
629
  @classmethod
630
  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
631
                              disable_meta_flush):
632
    """Compute the DRBD command line parameters for disk barriers
633

634
    Returns a list of the disk barrier parameters as requested via the
635
    disabled_barriers and disable_meta_flush arguments, and according to the
636
    supported ones in the DRBD version vmaj.vmin.vrel
637

638
    If the desired option is unsupported, raises errors.BlockDeviceError.
639

640
    """
641
    disabled_barriers_set = frozenset(disabled_barriers)
642
    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
643
      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
644
                                    " barriers" % disabled_barriers)
645

    
646
    args = []
647

    
648
    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
649
    # does not exist)
650
    if not vmaj == 8 and vmin in (0, 2, 3):
651
      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
652
                                    (vmaj, vmin, vrel))
653

    
654
    def _AppendOrRaise(option, min_version):
655
      """Helper for DRBD options"""
656
      if min_version is not None and vrel >= min_version:
657
        args.append(option)
658
      else:
659
        raise errors.BlockDeviceError("Could not use the option %s as the"
660
                                      " DRBD version %d.%d.%d does not support"
661
                                      " it." % (option, vmaj, vmin, vrel))
662

    
663
    # the minimum version for each feature is encoded via pairs of (minor
664
    # version -> x) where x is version in which support for the option was
665
    # introduced.
666
    meta_flush_supported = disk_flush_supported = {
667
      0: 12,
668
      2: 7,
669
      3: 0,
670
      }
671

    
672
    disk_drain_supported = {
673
      2: 7,
674
      3: 0,
675
      }
676

    
677
    disk_barriers_supported = {
678
      3: 0,
679
      }
680

    
681
    # meta flushes
682
    if disable_meta_flush:
683
      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
684
                     meta_flush_supported.get(vmin, None))
685

    
686
    # disk flushes
687
    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
688
      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
689
                     disk_flush_supported.get(vmin, None))
690

    
691
    # disk drain
692
    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
693
      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
694
                     disk_drain_supported.get(vmin, None))
695

    
696
    # disk barriers
697
    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
698
      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
699
                     disk_barriers_supported.get(vmin, None))
700

    
701
    return args
702

    
703
  def _AssembleNet(self, minor, net_info, protocol,
704
                   dual_pri=False, hmac=None, secret=None):
705
    """Configure the network part of the device.
706

707
    """
708
    lhost, lport, rhost, rport = net_info
709
    if None in net_info:
710
      # we don't want network connection and actually want to make
711
      # sure its shutdown
712
      self._ShutdownNet(minor)
713
      return
714

    
715
    # Workaround for a race condition. When DRBD is doing its dance to
716
    # establish a connection with its peer, it also sends the
717
    # synchronization speed over the wire. In some cases setting the
718
    # sync speed only after setting up both sides can race with DRBD
719
    # connecting, hence we set it here before telling DRBD anything
720
    # about its peer.
721
    sync_errors = self._SetMinorSyncParams(minor, self.params)
722
    if sync_errors:
723
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
724
                      (minor, utils.CommaJoin(sync_errors)))
725

    
726
    if netutils.IP6Address.IsValid(lhost):
727
      if not netutils.IP6Address.IsValid(rhost):
728
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
729
                        (minor, lhost, rhost))
730
      family = "ipv6"
731
    elif netutils.IP4Address.IsValid(lhost):
732
      if not netutils.IP4Address.IsValid(rhost):
733
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
734
                        (minor, lhost, rhost))
735
      family = "ipv4"
736
    else:
737
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
738

    
739
    args = ["drbdsetup", self._DevPath(minor), "net",
740
            "%s:%s:%s" % (family, lhost, lport),
741
            "%s:%s:%s" % (family, rhost, rport), protocol,
742
            "-A", "discard-zero-changes",
743
            "-B", "consensus",
744
            "--create-device",
745
            ]
746
    if dual_pri:
747
      args.append("-m")
748
    if hmac and secret:
749
      args.extend(["-a", hmac, "-x", secret])
750

    
751
    if self.params[constants.LDP_NET_CUSTOM]:
752
      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
753

    
754
    result = utils.RunCmd(args)
755
    if result.failed:
756
      base.ThrowError("drbd%d: can't setup network: %s - %s",
757
                      minor, result.fail_reason, result.output)
758

    
759
    def _CheckNetworkConfig():
760
      info = self._GetDevInfo(self._GetShowData(minor))
761
      if not "local_addr" in info or not "remote_addr" in info:
762
        raise utils.RetryAgain()
763

    
764
      if (info["local_addr"] != (lhost, lport) or
765
          info["remote_addr"] != (rhost, rport)):
766
        raise utils.RetryAgain()
767

    
768
    try:
769
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
770
    except utils.RetryTimeout:
771
      base.ThrowError("drbd%d: timeout while configuring network", minor)
772

    
773
  def AddChildren(self, devices):
774
    """Add a disk to the DRBD device.
775

776
    """
777
    if self.minor is None:
778
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
779
                      self._aminor)
780
    if len(devices) != 2:
781
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
782
    info = self._GetDevInfo(self._GetShowData(self.minor))
783
    if "local_dev" in info:
784
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
785
    backend, meta = devices
786
    if backend.dev_path is None or meta.dev_path is None:
787
      base.ThrowError("drbd%d: children not ready during AddChildren",
788
                      self.minor)
789
    backend.Open()
790
    meta.Open()
791
    self._CheckMetaSize(meta.dev_path)
792
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
793

    
794
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
795
    self._children = devices
796

    
797
  def RemoveChildren(self, devices):
798
    """Detach the drbd device from local storage.
799

800
    """
801
    if self.minor is None:
802
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
803
                      self._aminor)
804
    # early return if we don't actually have backing storage
805
    info = self._GetDevInfo(self._GetShowData(self.minor))
806
    if "local_dev" not in info:
807
      return
808
    if len(self._children) != 2:
809
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
810
                      self._children)
811
    if self._children.count(None) == 2: # we don't actually have children :)
812
      logging.warning("drbd%d: requested detach while detached", self.minor)
813
      return
814
    if len(devices) != 2:
815
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
816
                      self.minor)
817
    for child, dev in zip(self._children, devices):
818
      if dev != child.dev_path:
819
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
820
                        " RemoveChildren", self.minor, dev, child.dev_path)
821

    
822
    self._ShutdownLocal(self.minor)
823
    self._children = []
824

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

829
    This is the low-level implementation.
830

831
    @type minor: int
832
    @param minor: the drbd minor whose settings we change
833
    @type params: dict
834
    @param params: LD level disk parameters related to the synchronization
835
    @rtype: list
836
    @return: a list of error messages
837

838
    """
839

    
840
    args = ["drbdsetup", cls._DevPath(minor), "syncer"]
841
    if params[constants.LDP_DYNAMIC_RESYNC]:
842
      version = cls._GetVersion(cls._GetProcData())
843
      vmin = version["k_minor"]
844
      vrel = version["k_point"]
845

    
846
      # By definition we are using 8.x, so just check the rest of the version
847
      # number
848
      if vmin != 3 or vrel < 9:
849
        msg = ("The current DRBD version (8.%d.%d) does not support the "
850
               "dynamic resync speed controller" % (vmin, vrel))
851
        logging.error(msg)
852
        return [msg]
853

    
854
      if params[constants.LDP_PLAN_AHEAD] == 0:
855
        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
856
               " controller at DRBD level. If you want to disable it, please"
857
               " set the dynamic-resync disk parameter to False.")
858
        logging.error(msg)
859
        return [msg]
860

    
861
      # add the c-* parameters to args
862
      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
863
                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
864
                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
865
                   "--c-max-rate", params[constants.LDP_MAX_RATE],
866
                   "--c-min-rate", params[constants.LDP_MIN_RATE],
867
                   ])
868

    
869
    else:
870
      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
871

    
872
    args.append("--create-device")
873
    result = utils.RunCmd(args)
874
    if result.failed:
875
      msg = ("Can't change syncer rate: %s - %s" %
876
             (result.fail_reason, result.output))
877
      logging.error(msg)
878
      return [msg]
879

    
880
    return []
881

    
882
  def SetSyncParams(self, params):
883
    """Set the synchronization parameters of the DRBD syncer.
884

885
    @type params: dict
886
    @param params: LD level disk parameters related to the synchronization
887
    @rtype: list
888
    @return: a list of error messages, emitted both by the current node and by
889
    children. An empty list means no errors
890

891
    """
892
    if self.minor is None:
893
      err = "Not attached during SetSyncParams"
894
      logging.info(err)
895
      return [err]
896

    
897
    children_result = super(DRBD8, self).SetSyncParams(params)
898
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
899
    return children_result
900

    
901
  def PauseResumeSync(self, pause):
902
    """Pauses or resumes the sync of a DRBD device.
903

904
    @param pause: Wether to pause or resume
905
    @return: the success of the operation
906

907
    """
908
    if self.minor is None:
909
      logging.info("Not attached during PauseSync")
910
      return False
911

    
912
    children_result = super(DRBD8, self).PauseResumeSync(pause)
913

    
914
    if pause:
915
      cmd = "pause-sync"
916
    else:
917
      cmd = "resume-sync"
918

    
919
    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
920
    if result.failed:
921
      logging.error("Can't %s: %s - %s", cmd,
922
                    result.fail_reason, result.output)
923
    return not result.failed and children_result
924

    
925
  def GetProcStatus(self):
926
    """Return device data from /proc.
927

928
    """
929
    if self.minor is None:
930
      base.ThrowError("drbd%d: GetStats() called while not attached",
931
                      self._aminor)
932
    proc_info = self._JoinProcDataPerMinor(self._GetProcData())
933
    if self.minor not in proc_info:
934
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
935
    return DRBD8Status(proc_info[self.minor])
936

    
937
  def GetSyncStatus(self):
938
    """Returns the sync status of the device.
939

940

941
    If sync_percent is None, it means all is ok
942
    If estimated_time is None, it means we can't estimate
943
    the time needed, otherwise it's the time left in seconds.
944

945

946
    We set the is_degraded parameter to True on two conditions:
947
    network not connected or local disk missing.
948

949
    We compute the ldisk parameter based on whether we have a local
950
    disk or not.
951

952
    @rtype: objects.BlockDevStatus
953

954
    """
955
    if self.minor is None and not self.Attach():
956
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
957

    
958
    stats = self.GetProcStatus()
959
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
960

    
961
    if stats.is_disk_uptodate:
962
      ldisk_status = constants.LDS_OKAY
963
    elif stats.is_diskless:
964
      ldisk_status = constants.LDS_FAULTY
965
    else:
966
      ldisk_status = constants.LDS_UNKNOWN
967

    
968
    return objects.BlockDevStatus(dev_path=self.dev_path,
969
                                  major=self.major,
970
                                  minor=self.minor,
971
                                  sync_percent=stats.sync_percent,
972
                                  estimated_time=stats.est_time,
973
                                  is_degraded=is_degraded,
974
                                  ldisk_status=ldisk_status)
975

    
976
  def Open(self, force=False):
977
    """Make the local state primary.
978

979
    If the 'force' parameter is given, the '-o' option is passed to
980
    drbdsetup. Since this is a potentially dangerous operation, the
981
    force flag should be only given after creation, when it actually
982
    is mandatory.
983

984
    """
985
    if self.minor is None and not self.Attach():
986
      logging.error("DRBD cannot attach to a device during open")
987
      return False
988
    cmd = ["drbdsetup", self.dev_path, "primary"]
989
    if force:
990
      cmd.append("-o")
991
    result = utils.RunCmd(cmd)
992
    if result.failed:
993
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
994
                      result.output)
995

    
996
  def Close(self):
997
    """Make the local state secondary.
998

999
    This will, of course, fail if the device is in use.
1000

1001
    """
1002
    if self.minor is None and not self.Attach():
1003
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1004
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1005
    if result.failed:
1006
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1007
                      self.minor, result.output)
1008

    
1009
  def DisconnectNet(self):
1010
    """Removes network configuration.
1011

1012
    This method shutdowns the network side of the device.
1013

1014
    The method will wait up to a hardcoded timeout for the device to
1015
    go into standalone after the 'disconnect' command before
1016
    re-configuring it, as sometimes it takes a while for the
1017
    disconnect to actually propagate and thus we might issue a 'net'
1018
    command while the device is still connected. If the device will
1019
    still be attached to the network and we time out, we raise an
1020
    exception.
1021

1022
    """
1023
    if self.minor is None:
1024
      base.ThrowError("drbd%d: disk not attached in re-attach net",
1025
                      self._aminor)
1026

    
1027
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1028
      base.ThrowError("drbd%d: DRBD disk missing network info in"
1029
                      " DisconnectNet()", self.minor)
1030

    
1031
    class _DisconnectStatus:
1032
      def __init__(self, ever_disconnected):
1033
        self.ever_disconnected = ever_disconnected
1034

    
1035
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
1036

    
1037
    def _WaitForDisconnect():
1038
      if self.GetProcStatus().is_standalone:
1039
        return
1040

    
1041
      # retry the disconnect, it seems possible that due to a well-time
1042
      # disconnect on the peer, my disconnect command might be ignored and
1043
      # forgotten
1044
      dstatus.ever_disconnected = \
1045
        base.IgnoreError(self._ShutdownNet, self.minor) or \
1046
        dstatus.ever_disconnected
1047

    
1048
      raise utils.RetryAgain()
1049

    
1050
    # Keep start time
1051
    start_time = time.time()
1052

    
1053
    try:
1054
      # Start delay at 100 milliseconds and grow up to 2 seconds
1055
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1056
                  self._NET_RECONFIG_TIMEOUT)
1057
    except utils.RetryTimeout:
1058
      if dstatus.ever_disconnected:
1059
        msg = ("drbd%d: device did not react to the"
1060
               " 'disconnect' command in a timely manner")
1061
      else:
1062
        msg = "drbd%d: can't shutdown network, even after multiple retries"
1063

    
1064
      base.ThrowError(msg, self.minor)
1065

    
1066
    reconfig_time = time.time() - start_time
1067
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1068
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1069
                   self.minor, reconfig_time)
1070

    
1071
  def AttachNet(self, multimaster):
1072
    """Reconnects the network.
1073

1074
    This method connects the network side of the device with a
1075
    specified multi-master flag. The device needs to be 'Standalone'
1076
    but have valid network configuration data.
1077

1078
    Args:
1079
      - multimaster: init the network in dual-primary mode
1080

1081
    """
1082
    if self.minor is None:
1083
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1084

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

    
1088
    status = self.GetProcStatus()
1089

    
1090
    if not status.is_standalone:
1091
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
1092
                      self.minor)
1093

    
1094
    self._AssembleNet(self.minor,
1095
                      (self._lhost, self._lport, self._rhost, self._rport),
1096
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1097
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1098

    
1099
  def Attach(self):
1100
    """Check if our minor is configured.
1101

1102
    This doesn't do any device configurations - it only checks if the
1103
    minor is in a state different from Unconfigured.
1104

1105
    Note that this function will not change the state of the system in
1106
    any way (except in case of side-effects caused by reading from
1107
    /proc).
1108

1109
    """
1110
    used_devs = self.GetUsedDevs()
1111
    if self._aminor in used_devs:
1112
      minor = self._aminor
1113
    else:
1114
      minor = None
1115

    
1116
    self._SetFromMinor(minor)
1117
    return minor is not None
1118

    
1119
  def Assemble(self):
1120
    """Assemble the drbd.
1121

1122
    Method:
1123
      - if we have a configured device, we try to ensure that it matches
1124
        our config
1125
      - if not, we create it from zero
1126
      - anyway, set the device parameters
1127

1128
    """
1129
    super(DRBD8, self).Assemble()
1130

    
1131
    self.Attach()
1132
    if self.minor is None:
1133
      # local device completely unconfigured
1134
      self._FastAssemble()
1135
    else:
1136
      # we have to recheck the local and network status and try to fix
1137
      # the device
1138
      self._SlowAssemble()
1139

    
1140
    sync_errors = self.SetSyncParams(self.params)
1141
    if sync_errors:
1142
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1143
                      (self.minor, utils.CommaJoin(sync_errors)))
1144

    
1145
  def _SlowAssemble(self):
1146
    """Assembles the DRBD device from a (partially) configured device.
1147

1148
    In case of partially attached (local device matches but no network
1149
    setup), we perform the network attach. If successful, we re-test
1150
    the attach if can return success.
1151

1152
    """
1153
    # TODO: Rewrite to not use a for loop just because there is 'break'
1154
    # pylint: disable=W0631
1155
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
1156
    for minor in (self._aminor,):
1157
      info = self._GetDevInfo(self._GetShowData(minor))
1158
      match_l = self._MatchesLocal(info)
1159
      match_r = self._MatchesNet(info)
1160

    
1161
      if match_l and match_r:
1162
        # everything matches
1163
        break
1164

    
1165
      if match_l and not match_r and "local_addr" not in info:
1166
        # disk matches, but not attached to network, attach and recheck
1167
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1168
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1169
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1170
          break
1171
        else:
1172
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1173
                          " show' disagrees", minor)
1174

    
1175
      if match_r and "local_dev" not in info:
1176
        # no local disk, but network attached and it matches
1177
        self._AssembleLocal(minor, self._children[0].dev_path,
1178
                            self._children[1].dev_path, self.size)
1179
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1180
          break
1181
        else:
1182
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1183
                          " show' disagrees", minor)
1184

    
1185
      # this case must be considered only if we actually have local
1186
      # storage, i.e. not in diskless mode, because all diskless
1187
      # devices are equal from the point of view of local
1188
      # configuration
1189
      if (match_l and "local_dev" in info and
1190
          not match_r and "local_addr" in info):
1191
        # strange case - the device network part points to somewhere
1192
        # else, even though its local storage is ours; as we own the
1193
        # drbd space, we try to disconnect from the remote peer and
1194
        # reconnect to our correct one
1195
        try:
1196
          self._ShutdownNet(minor)
1197
        except errors.BlockDeviceError, err:
1198
          base.ThrowError("drbd%d: device has correct local storage, wrong"
1199
                          " remote peer and is unable to disconnect in order"
1200
                          " to attach to the correct peer: %s", minor, str(err))
1201
        # note: _AssembleNet also handles the case when we don't want
1202
        # local storage (i.e. one or more of the _[lr](host|port) is
1203
        # None)
1204
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1205
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1206
        if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1207
          break
1208
        else:
1209
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1210
                          " show' disagrees", minor)
1211

    
1212
    else:
1213
      minor = None
1214

    
1215
    self._SetFromMinor(minor)
1216
    if minor is None:
1217
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1218
                      self._aminor)
1219

    
1220
  def _FastAssemble(self):
1221
    """Assemble the drbd device from zero.
1222

1223
    This is run when in Assemble we detect our minor is unused.
1224

1225
    """
1226
    minor = self._aminor
1227
    if self._children and self._children[0] and self._children[1]:
1228
      self._AssembleLocal(minor, self._children[0].dev_path,
1229
                          self._children[1].dev_path, self.size)
1230
    if self._lhost and self._lport and self._rhost and self._rport:
1231
      self._AssembleNet(minor,
1232
                        (self._lhost, self._lport, self._rhost, self._rport),
1233
                        constants.DRBD_NET_PROTOCOL,
1234
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1235
    self._SetFromMinor(minor)
1236

    
1237
  @classmethod
1238
  def _ShutdownLocal(cls, minor):
1239
    """Detach from the local device.
1240

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

1244
    """
1245
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1246
    if result.failed:
1247
      base.ThrowError("drbd%d: can't detach local disk: %s",
1248
                      minor, result.output)
1249

    
1250
  @classmethod
1251
  def _ShutdownNet(cls, minor):
1252
    """Disconnect from the remote peer.
1253

1254
    This fails if we don't have a local device.
1255

1256
    """
1257
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1258
    if result.failed:
1259
      base.ThrowError("drbd%d: can't shutdown network: %s",
1260
                      minor, result.output)
1261

    
1262
  @classmethod
1263
  def _ShutdownAll(cls, minor):
1264
    """Deactivate the device.
1265

1266
    This will, of course, fail if the device is in use.
1267

1268
    """
1269
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1270
    if result.failed:
1271
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
1272
                      minor, result.output)
1273

    
1274
  def Shutdown(self):
1275
    """Shutdown the DRBD device.
1276

1277
    """
1278
    if self.minor is None and not self.Attach():
1279
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1280
      return
1281
    minor = self.minor
1282
    self.minor = None
1283
    self.dev_path = None
1284
    self._ShutdownAll(minor)
1285

    
1286
  def Remove(self):
1287
    """Stub remove for DRBD devices.
1288

1289
    """
1290
    self.Shutdown()
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

    
1300
  @classmethod
1301
  def Create(cls, unique_id, children, size, params, excl_stor):
1302
    """Create a new DRBD8 device.
1303

1304
    Since DRBD devices are not created per se, just assembled, this
1305
    function only initializes the metadata.
1306

1307
    """
1308
    if len(children) != 2:
1309
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1310
    if excl_stor:
1311
      raise errors.ProgrammerError("DRBD device requested with"
1312
                                   " exclusive_storage")
1313
    # check that the minor is unused
1314
    aminor = unique_id[4]
1315
    proc_info = cls._JoinProcDataPerMinor(cls._GetProcData())
1316
    if aminor in proc_info:
1317
      status = DRBD8Status(proc_info[aminor])
1318
      in_use = status.is_in_use
1319
    else:
1320
      in_use = False
1321
    if in_use:
1322
      base.ThrowError("drbd%d: minor is already in use at Create() time",
1323
                      aminor)
1324
    meta = children[1]
1325
    meta.Assemble()
1326
    if not meta.Attach():
1327
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
1328
                      aminor, meta)
1329
    cls._CheckMetaSize(meta.dev_path)
1330
    cls._InitMeta(aminor, meta.dev_path)
1331
    return cls(unique_id, children, size, params)
1332

    
1333
  def Grow(self, amount, dryrun, backingstore):
1334
    """Resize the DRBD device and its backing storage.
1335

1336
    """
1337
    if self.minor is None:
1338
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
1339
    if len(self._children) != 2 or None in self._children:
1340
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
1341
    self._children[0].Grow(amount, dryrun, backingstore)
1342
    if dryrun or backingstore:
1343
      # DRBD does not support dry-run mode and is not backing storage,
1344
      # so we'll return here
1345
      return
1346
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1347
                           "%dm" % (self.size + amount)])
1348
    if result.failed:
1349
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1350

    
1351

    
1352
def _CanReadDevice(path):
1353
  """Check if we can read from the given device.
1354

1355
  This tries to read the first 128k of the device.
1356

1357
  """
1358
  try:
1359
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1360
    return True
1361
  except EnvironmentError:
1362
    logging.warning("Can't read from device %s", path, exc_info=True)
1363
    return False