Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ 89ff748d

History | View | Annotate | Download (45.7 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 BaseDRBD(base.BlockDev): # pylint: disable=W0223
46
  """Base DRBD class.
47

48
  This class contains a few bits of common functionality between the
49
  0.7 and 8.x versions of DRBD.
50

51
  """
52
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)(?:\.\d+)?"
53
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
54
  _VALID_LINE_RE = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
55
  _UNUSED_LINE_RE = re.compile("^ *([0-9]+): cs:Unconfigured$")
56

    
57
  _DRBD_MAJOR = 147
58
  _ST_UNCONFIGURED = "Unconfigured"
59
  _ST_WFCONNECTION = "WFConnection"
60
  _ST_CONNECTED = "Connected"
61

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

    
65
  @staticmethod
66
  def _GetProcData(filename=_STATUS_FILE):
67
    """Return data from /proc/drbd.
68

69
    """
70
    try:
71
      data = utils.ReadFile(filename).splitlines()
72
    except EnvironmentError, err:
73
      if err.errno == errno.ENOENT:
74
        base.ThrowError("The file %s cannot be opened, check if the module"
75
                        " is loaded (%s)", filename, str(err))
76
      else:
77
        base.ThrowError("Can't read the DRBD proc file %s: %s",
78
                        filename, str(err))
79
    if not data:
80
      base.ThrowError("Can't read any data from %s", filename)
81
    return data
82

    
83
  @classmethod
84
  def _MassageProcData(cls, data):
85
    """Transform the output of _GetProdData into a nicer form.
86

87
    @return: a dictionary of minor: joined lines from /proc/drbd
88
        for that minor
89

90
    """
91
    results = {}
92
    old_minor = old_line = None
93
    for line in data:
94
      if not line: # completely empty lines, as can be returned by drbd8.0+
95
        continue
96
      lresult = cls._VALID_LINE_RE.match(line)
97
      if lresult is not None:
98
        if old_minor is not None:
99
          results[old_minor] = old_line
100
        old_minor = int(lresult.group(1))
101
        old_line = line
102
      else:
103
        if old_minor is not None:
104
          old_line += " " + line.strip()
105
    # add last line
106
    if old_minor is not None:
107
      results[old_minor] = old_line
108
    return results
109

    
110
  @classmethod
111
  def _GetVersion(cls, proc_data):
112
    """Return the DRBD version.
113

114
    This will return a dict with keys:
115
      - k_major
116
      - k_minor
117
      - k_point
118
      - api
119
      - proto
120
      - proto2 (only on drbd > 8.2.X)
121

122
    """
123
    first_line = proc_data[0].strip()
124
    version = cls._VERSION_RE.match(first_line)
125
    if not version:
126
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
127
                                    first_line)
128

    
129
    values = version.groups()
130
    retval = {
131
      "k_major": int(values[0]),
132
      "k_minor": int(values[1]),
133
      "k_point": int(values[2]),
134
      "api": int(values[3]),
135
      "proto": int(values[4]),
136
      }
137
    if values[5] is not None:
138
      retval["proto2"] = values[5]
139

    
140
    return retval
141

    
142
  @staticmethod
143
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
144
    """Returns DRBD usermode_helper currently set.
145

146
    """
147
    try:
148
      helper = utils.ReadFile(filename).splitlines()[0]
149
    except EnvironmentError, err:
150
      if err.errno == errno.ENOENT:
151
        base.ThrowError("The file %s cannot be opened, check if the module"
152
                        " is loaded (%s)", filename, str(err))
153
      else:
154
        base.ThrowError("Can't read DRBD helper file %s: %s",
155
                        filename, str(err))
156
    if not helper:
157
      base.ThrowError("Can't read any data from %s", filename)
158
    return helper
159

    
160
  @staticmethod
161
  def _DevPath(minor):
162
    """Return the path to a drbd device for a given minor.
163

164
    """
165
    return "/dev/drbd%d" % minor
166

    
167
  @classmethod
168
  def GetUsedDevs(cls):
169
    """Compute the list of used DRBD devices.
170

171
    """
172
    data = cls._GetProcData()
173

    
174
    used_devs = {}
175
    for line in data:
176
      match = cls._VALID_LINE_RE.match(line)
177
      if not match:
178
        continue
179
      minor = int(match.group(1))
180
      state = match.group(2)
181
      if state == cls._ST_UNCONFIGURED:
182
        continue
183
      used_devs[minor] = state, line
184

    
185
    return used_devs
186

    
187
  def _SetFromMinor(self, minor):
188
    """Set our parameters based on the given minor.
189

190
    This sets our minor variable and our dev_path.
191

192
    """
193
    if minor is None:
194
      self.minor = self.dev_path = None
195
      self.attached = False
196
    else:
197
      self.minor = minor
198
      self.dev_path = self._DevPath(minor)
199
      self.attached = True
200

    
201
  @staticmethod
202
  def _CheckMetaSize(meta_device):
203
    """Check if the given meta device looks like a valid one.
204

205
    This currently only checks the size, which must be around
206
    128MiB.
207

208
    """
209
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
210
    if result.failed:
211
      base.ThrowError("Failed to get device size: %s - %s",
212
                      result.fail_reason, result.output)
213
    try:
214
      sectors = int(result.stdout)
215
    except (TypeError, ValueError):
216
      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
217
    num_bytes = sectors * 512
218
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
219
      base.ThrowError("Meta device too small (%.2fMib)",
220
                      (num_bytes / 1024 / 1024))
221
    # the maximum *valid* size of the meta device when living on top
222
    # of LVM is hard to compute: it depends on the number of stripes
223
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
224
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
225
    # size meta device; as such, we restrict it to 1GB (a little bit
226
    # too generous, but making assumptions about PE size is hard)
227
    if num_bytes > 1024 * 1024 * 1024:
228
      base.ThrowError("Meta device too big (%.2fMiB)",
229
                      (num_bytes / 1024 / 1024))
230

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

234
    This is not supported for drbd devices.
235

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

    
239

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

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

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

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

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

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

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

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

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

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

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

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

    
347

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

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

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

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

    
364
  # timeout constants
365
  _NET_RECONFIG_TIMEOUT = 60
366

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

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

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

    
401
  @classmethod
402
  def _InitMeta(cls, minor, dev_path):
403
    """Initialize a meta device.
404

405
    This will not work if the given minor is in use.
406

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

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

    
424
  @classmethod
425
  def _FindUnusedMinor(cls):
426
    """Find an unused DRBD device.
427

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

431
    """
432
    data = cls._GetProcData()
433

    
434
    highest = None
435
    for line in data:
436
      match = cls._UNUSED_LINE_RE.match(line)
437
      if match:
438
        return int(match.group(1))
439
      match = cls._VALID_LINE_RE.match(line)
440
      if match:
441
        minor = int(match.group(1))
442
        highest = max(highest, minor)
443
    if highest is None: # there are no minors in use at all
444
      return 0
445
    if highest >= cls._MAX_MINORS:
446
      logging.error("Error: no free drbd minors!")
447
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
448
    return highest + 1
449

    
450
  @classmethod
451
  def _GetShowParser(cls):
452
    """Return a parser for `drbd show` output.
453

454
    This will either create or return an already-created parser for the
455
    output of the command `drbd show`.
456

457
    """
458
    if cls._PARSE_SHOW is not None:
459
      return cls._PARSE_SHOW
460

    
461
    # pyparsing setup
462
    lbrace = pyp.Literal("{").suppress()
463
    rbrace = pyp.Literal("}").suppress()
464
    lbracket = pyp.Literal("[").suppress()
465
    rbracket = pyp.Literal("]").suppress()
466
    semi = pyp.Literal(";").suppress()
467
    colon = pyp.Literal(":").suppress()
468
    # this also converts the value to an int
469
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
470

    
471
    comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
472
    defa = pyp.Literal("_is_default").suppress()
473
    dbl_quote = pyp.Literal('"').suppress()
474

    
475
    keyword = pyp.Word(pyp.alphanums + "-")
476

    
477
    # value types
478
    value = pyp.Word(pyp.alphanums + "_-/.:")
479
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
480
    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
481
                 pyp.Word(pyp.nums + ".") + colon + number)
482
    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
483
                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
484
                 pyp.Optional(rbracket) + colon + number)
485
    # meta device, extended syntax
486
    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
487
    # device name, extended syntax
488
    device_value = pyp.Literal("minor").suppress() + number
489

    
490
    # a statement
491
    stmt = (~rbrace + keyword + ~lbrace +
492
            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
493
                         device_value) +
494
            pyp.Optional(defa) + semi +
495
            pyp.Optional(pyp.restOfLine).suppress())
496

    
497
    # an entire section
498
    section_name = pyp.Word(pyp.alphas + "_")
499
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
500

    
501
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
502
    bnf.ignore(comment)
503

    
504
    cls._PARSE_SHOW = bnf
505

    
506
    return bnf
507

    
508
  @classmethod
509
  def _GetShowData(cls, minor):
510
    """Return the `drbdsetup show` data for a minor.
511

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

    
520
  @classmethod
521
  def _GetDevInfo(cls, out):
522
    """Parse details about a given DRBD minor.
523

524
    This return, if available, the local backing device (as a path)
525
    and the local and remote (ip, port) information from a string
526
    containing the output of the `drbdsetup show` command as returned
527
    by _GetShowData.
528

529
    """
530
    data = {}
531
    if not out:
532
      return data
533

    
534
    bnf = cls._GetShowParser()
535
    # run pyparse
536

    
537
    try:
538
      results = bnf.parseString(out)
539
    except pyp.ParseException, err:
540
      base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
541

    
542
    # and massage the results into our desired format
543
    for section in results:
544
      sname = section[0]
545
      if sname == "_this_host":
546
        for lst in section[1:]:
547
          if lst[0] == "disk":
548
            data["local_dev"] = lst[1]
549
          elif lst[0] == "meta-disk":
550
            data["meta_dev"] = lst[1]
551
            data["meta_index"] = lst[2]
552
          elif lst[0] == "address":
553
            data["local_addr"] = tuple(lst[1:])
554
      elif sname == "_remote_host":
555
        for lst in section[1:]:
556
          if lst[0] == "address":
557
            data["remote_addr"] = tuple(lst[1:])
558
    return data
559

    
560
  def _MatchesLocal(self, info):
561
    """Test if our local config matches with an existing device.
562

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

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

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

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

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

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

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

    
602
    if self._lhost is None:
603
      return False
604

    
605
    if not ("local_addr" in info and
606
            "remote_addr" in info):
607
      return False
608

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

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

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

    
625
    version = self._GetVersion(self._GetProcData())
626
    vmaj = version["k_major"]
627
    vmin = version["k_minor"]
628
    vrel = version["k_point"]
629

    
630
    barrier_args = \
631
      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
632
                                   self.params[constants.LDP_BARRIERS],
633
                                   self.params[constants.LDP_NO_META_FLUSH])
634
    args.extend(barrier_args)
635

    
636
    if self.params[constants.LDP_DISK_CUSTOM]:
637
      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
638

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

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

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

653
    If the desired option is unsupported, raises errors.BlockDeviceError.
654

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

    
661
    args = []
662

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

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

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

    
687
    disk_drain_supported = {
688
      2: 7,
689
      3: 0,
690
      }
691

    
692
    disk_barriers_supported = {
693
      3: 0,
694
      }
695

    
696
    # meta flushes
697
    if disable_meta_flush:
698
      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
699
                     meta_flush_supported.get(vmin, None))
700

    
701
    # disk flushes
702
    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
703
      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
704
                     disk_flush_supported.get(vmin, None))
705

    
706
    # disk drain
707
    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
708
      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
709
                     disk_drain_supported.get(vmin, None))
710

    
711
    # disk barriers
712
    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
713
      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
714
                     disk_barriers_supported.get(vmin, None))
715

    
716
    return args
717

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

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

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

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

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

    
766
    if self.params[constants.LDP_NET_CUSTOM]:
767
      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
768

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

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

    
779
      if (info["local_addr"] != (lhost, lport) or
780
          info["remote_addr"] != (rhost, rport)):
781
        raise utils.RetryAgain()
782

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

    
788
  def AddChildren(self, devices):
789
    """Add a disk to the DRBD device.
790

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

    
809
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
810
    self._children = devices
811

    
812
  def RemoveChildren(self, devices):
813
    """Detach the drbd device from local storage.
814

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

    
837
    self._ShutdownLocal(self.minor)
838
    self._children = []
839

    
840
  @classmethod
841
  def _SetMinorSyncParams(cls, minor, params):
842
    """Set the parameters of the DRBD syncer.
843

844
    This is the low-level implementation.
845

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

853
    """
854

    
855
    args = ["drbdsetup", cls._DevPath(minor), "syncer"]
856
    if params[constants.LDP_DYNAMIC_RESYNC]:
857
      version = cls._GetVersion(cls._GetProcData())
858
      vmin = version["k_minor"]
859
      vrel = version["k_point"]
860

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

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

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

    
884
    else:
885
      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
886

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

    
895
    return []
896

    
897
  def SetSyncParams(self, params):
898
    """Set the synchronization parameters of the DRBD syncer.
899

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

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

    
912
    children_result = super(DRBD8, self).SetSyncParams(params)
913
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
914
    return children_result
915

    
916
  def PauseResumeSync(self, pause):
917
    """Pauses or resumes the sync of a DRBD device.
918

919
    @param pause: Wether to pause or resume
920
    @return: the success of the operation
921

922
    """
923
    if self.minor is None:
924
      logging.info("Not attached during PauseSync")
925
      return False
926

    
927
    children_result = super(DRBD8, self).PauseResumeSync(pause)
928

    
929
    if pause:
930
      cmd = "pause-sync"
931
    else:
932
      cmd = "resume-sync"
933

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

    
940
  def GetProcStatus(self):
941
    """Return device data from /proc.
942

943
    """
944
    if self.minor is None:
945
      base.ThrowError("drbd%d: GetStats() called while not attached",
946
                      self._aminor)
947
    proc_info = self._MassageProcData(self._GetProcData())
948
    if self.minor not in proc_info:
949
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
950
    return DRBD8Status(proc_info[self.minor])
951

    
952
  def GetSyncStatus(self):
953
    """Returns the sync status of the device.
954

955

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

960

961
    We set the is_degraded parameter to True on two conditions:
962
    network not connected or local disk missing.
963

964
    We compute the ldisk parameter based on whether we have a local
965
    disk or not.
966

967
    @rtype: objects.BlockDevStatus
968

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

    
973
    stats = self.GetProcStatus()
974
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
975

    
976
    if stats.is_disk_uptodate:
977
      ldisk_status = constants.LDS_OKAY
978
    elif stats.is_diskless:
979
      ldisk_status = constants.LDS_FAULTY
980
    else:
981
      ldisk_status = constants.LDS_UNKNOWN
982

    
983
    return objects.BlockDevStatus(dev_path=self.dev_path,
984
                                  major=self.major,
985
                                  minor=self.minor,
986
                                  sync_percent=stats.sync_percent,
987
                                  estimated_time=stats.est_time,
988
                                  is_degraded=is_degraded,
989
                                  ldisk_status=ldisk_status)
990

    
991
  def Open(self, force=False):
992
    """Make the local state primary.
993

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

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

    
1011
  def Close(self):
1012
    """Make the local state secondary.
1013

1014
    This will, of course, fail if the device is in use.
1015

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

    
1024
  def DisconnectNet(self):
1025
    """Removes network configuration.
1026

1027
    This method shutdowns the network side of the device.
1028

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

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

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

    
1046
    class _DisconnectStatus:
1047
      def __init__(self, ever_disconnected):
1048
        self.ever_disconnected = ever_disconnected
1049

    
1050
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
1051

    
1052
    def _WaitForDisconnect():
1053
      if self.GetProcStatus().is_standalone:
1054
        return
1055

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

    
1063
      raise utils.RetryAgain()
1064

    
1065
    # Keep start time
1066
    start_time = time.time()
1067

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

    
1079
      base.ThrowError(msg, self.minor)
1080

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

    
1086
  def AttachNet(self, multimaster):
1087
    """Reconnects the network.
1088

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

1093
    Args:
1094
      - multimaster: init the network in dual-primary mode
1095

1096
    """
1097
    if self.minor is None:
1098
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1099

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

    
1103
    status = self.GetProcStatus()
1104

    
1105
    if not status.is_standalone:
1106
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
1107
                      self.minor)
1108

    
1109
    self._AssembleNet(self.minor,
1110
                      (self._lhost, self._lport, self._rhost, self._rport),
1111
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1112
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1113

    
1114
  def Attach(self):
1115
    """Check if our minor is configured.
1116

1117
    This doesn't do any device configurations - it only checks if the
1118
    minor is in a state different from Unconfigured.
1119

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

1124
    """
1125
    used_devs = self.GetUsedDevs()
1126
    if self._aminor in used_devs:
1127
      minor = self._aminor
1128
    else:
1129
      minor = None
1130

    
1131
    self._SetFromMinor(minor)
1132
    return minor is not None
1133

    
1134
  def Assemble(self):
1135
    """Assemble the drbd.
1136

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

1143
    """
1144
    super(DRBD8, self).Assemble()
1145

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

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

    
1160
  def _SlowAssemble(self):
1161
    """Assembles the DRBD device from a (partially) configured device.
1162

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

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

    
1176
      if match_l and match_r:
1177
        # everything matches
1178
        break
1179

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

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

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

    
1227
    else:
1228
      minor = None
1229

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

    
1235
  def _FastAssemble(self):
1236
    """Assemble the drbd device from zero.
1237

1238
    This is run when in Assemble we detect our minor is unused.
1239

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

    
1252
  @classmethod
1253
  def _ShutdownLocal(cls, minor):
1254
    """Detach from the local device.
1255

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

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

    
1265
  @classmethod
1266
  def _ShutdownNet(cls, minor):
1267
    """Disconnect from the remote peer.
1268

1269
    This fails if we don't have a local device.
1270

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

    
1277
  @classmethod
1278
  def _ShutdownAll(cls, minor):
1279
    """Deactivate the device.
1280

1281
    This will, of course, fail if the device is in use.
1282

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

    
1289
  def Shutdown(self):
1290
    """Shutdown the DRBD device.
1291

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

    
1301
  def Remove(self):
1302
    """Stub remove for DRBD devices.
1303

1304
    """
1305
    self.Shutdown()
1306

    
1307
  @classmethod
1308
  def Create(cls, unique_id, children, size, params, excl_stor):
1309
    """Create a new DRBD8 device.
1310

1311
    Since DRBD devices are not created per se, just assembled, this
1312
    function only initializes the metadata.
1313

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

    
1340
  def Grow(self, amount, dryrun, backingstore):
1341
    """Resize the DRBD device and its backing storage.
1342

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

    
1358

    
1359
def _CanReadDevice(path):
1360
  """Check if we can read from the given device.
1361

1362
  This tries to read the first 128k of the device.
1363

1364
  """
1365
  try:
1366
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1367
    return True
1368
  except EnvironmentError:
1369
    logging.warning("Can't read from device %s", path, exc_info=True)
1370
    return False