Statistics
| Branch: | Tag: | Revision:

root / lib / block / drbd.py @ d01e51a5

History | View | Annotate | Download (46.5 kB)

1
#
2
#
3

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

    
21

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

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

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

    
39

    
40
# Size of reads in _CanReadDevice
41

    
42
_DEVICE_READ_SIZE = 128 * 1024
43

    
44

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

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

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

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

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

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

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

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

    
119
    self.is_standalone = self.cstatus == self.CS_STANDALONE
120
    self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
121
    self.is_connected = self.cstatus == self.CS_CONNECTED
122
    self.is_unconfigured = self.cstatus == self.CS_UNCONFIGURED
123
    self.is_primary = self.lrole == self.RO_PRIMARY
124
    self.is_secondary = self.lrole == self.RO_SECONDARY
125
    self.peer_primary = self.rrole == self.RO_PRIMARY
126
    self.peer_secondary = self.rrole == self.RO_SECONDARY
127
    self.both_primary = self.is_primary and self.peer_primary
128
    self.both_secondary = self.is_secondary and self.peer_secondary
129

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

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

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

    
154

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

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

160
  """
161

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

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

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

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

181
    """
182
    return self._version
183

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

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

190
    """
191
    return self._minors
192

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

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

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

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

    
217
    return retval
218

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

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

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

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

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

    
267

    
268
class DRBD8ShowInfo(object):
269
  """Helper class which parses the output of drbdsetup show
270

271
  """
272
  _PARSE_SHOW = None
273

    
274
  @classmethod
275
  def _GetShowParser(cls):
276
    """Return a parser for `drbd show` output.
277

278
    This will either create or return an already-created parser for the
279
    output of the command `drbd show`.
280

281
    """
282
    if cls._PARSE_SHOW is not None:
283
      return cls._PARSE_SHOW
284

    
285
    # pyparsing setup
286
    lbrace = pyp.Literal("{").suppress()
287
    rbrace = pyp.Literal("}").suppress()
288
    lbracket = pyp.Literal("[").suppress()
289
    rbracket = pyp.Literal("]").suppress()
290
    semi = pyp.Literal(";").suppress()
291
    colon = pyp.Literal(":").suppress()
292
    # this also converts the value to an int
293
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
294

    
295
    comment = pyp.Literal("#") + pyp.Optional(pyp.restOfLine)
296
    defa = pyp.Literal("_is_default").suppress()
297
    dbl_quote = pyp.Literal('"').suppress()
298

    
299
    keyword = pyp.Word(pyp.alphanums + "-")
300

    
301
    # value types
302
    value = pyp.Word(pyp.alphanums + "_-/.:")
303
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
304
    ipv4_addr = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
305
                 pyp.Word(pyp.nums + ".") + colon + number)
306
    ipv6_addr = (pyp.Optional(pyp.Literal("ipv6")).suppress() +
307
                 pyp.Optional(lbracket) + pyp.Word(pyp.hexnums + ":") +
308
                 pyp.Optional(rbracket) + colon + number)
309
    # meta device, extended syntax
310
    meta_value = ((value ^ quoted) + lbracket + number + rbracket)
311
    # device name, extended syntax
312
    device_value = pyp.Literal("minor").suppress() + number
313

    
314
    # a statement
315
    stmt = (~rbrace + keyword + ~lbrace +
316
            pyp.Optional(ipv4_addr ^ ipv6_addr ^ value ^ quoted ^ meta_value ^
317
                         device_value) +
318
            pyp.Optional(defa) + semi +
319
            pyp.Optional(pyp.restOfLine).suppress())
320

    
321
    # an entire section
322
    section_name = pyp.Word(pyp.alphas + "_")
323
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
324

    
325
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
326
    bnf.ignore(comment)
327

    
328
    cls._PARSE_SHOW = bnf
329

    
330
    return bnf
331

    
332
  @classmethod
333
  def GetDevInfo(cls, show_data):
334
    """Parse details about a given DRBD minor.
335

336
    This returns, if available, the local backing device (as a path)
337
    and the local and remote (ip, port) information from a string
338
    containing the output of the `drbdsetup show` command as returned
339
    by DRBD8._GetShowData.
340

341
    This will return a dict with keys:
342
      - local_dev
343
      - meta_dev
344
      - meta_index
345
      - local_addr
346
      - remote_addr
347

348
    """
349
    retval = {}
350
    if not show_data:
351
      return retval
352

    
353
    try:
354
      # run pyparse
355
      results = (cls._GetShowParser()).parseString(show_data)
356
    except pyp.ParseException, err:
357
      base.ThrowError("Can't parse drbdsetup show output: %s", str(err))
358

    
359
    # and massage the results into our desired format
360
    for section in results:
361
      sname = section[0]
362
      if sname == "_this_host":
363
        for lst in section[1:]:
364
          if lst[0] == "disk":
365
            retval["local_dev"] = lst[1]
366
          elif lst[0] == "meta-disk":
367
            retval["meta_dev"] = lst[1]
368
            retval["meta_index"] = lst[2]
369
          elif lst[0] == "address":
370
            retval["local_addr"] = tuple(lst[1:])
371
      elif sname == "_remote_host":
372
        for lst in section[1:]:
373
          if lst[0] == "address":
374
            retval["remote_addr"] = tuple(lst[1:])
375
    return retval
376

    
377

    
378
class DRBD8(base.BlockDev):
379
  """DRBD v8.x block device.
380

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

385
  The unique_id for the drbd device is a (local_ip, local_port,
386
  remote_ip, remote_port, local_minor, secret) tuple, and it must have
387
  two children: the data device and the meta_device. The meta device
388
  is checked for valid size and is zeroed on create.
389

390
  """
391
  _DRBD_MAJOR = 147
392

    
393
  _USERMODE_HELPER_FILE = "/sys/module/drbd/parameters/usermode_helper"
394

    
395
  _MAX_MINORS = 255
396

    
397
  # timeout constants
398
  _NET_RECONFIG_TIMEOUT = 60
399

    
400
  # command line options for barriers
401
  _DISABLE_DISK_OPTION = "--no-disk-barrier"  # -a
402
  _DISABLE_DRAIN_OPTION = "--no-disk-drain"   # -D
403
  _DISABLE_FLUSH_OPTION = "--no-disk-flushes" # -i
404
  _DISABLE_META_FLUSH_OPTION = "--no-md-flushes"  # -m
405

    
406
  def __init__(self, unique_id, children, size, params):
407
    if children and children.count(None) > 0:
408
      children = []
409
    if len(children) not in (0, 2):
410
      raise ValueError("Invalid configuration data %s" % str(children))
411
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
412
      raise ValueError("Invalid configuration data %s" % str(unique_id))
413
    (self._lhost, self._lport,
414
     self._rhost, self._rport,
415
     self._aminor, self._secret) = unique_id
416
    if children:
417
      if not _CanReadDevice(children[1].dev_path):
418
        logging.info("drbd%s: Ignoring unreadable meta device", self._aminor)
419
        children = []
420
    super(DRBD8, self).__init__(unique_id, children, size, params)
421
    self.major = self._DRBD_MAJOR
422

    
423
    drbd_info = DRBD8Info.CreateFromFile()
424
    version = drbd_info.GetVersion()
425
    if version["k_major"] != 8:
426
      base.ThrowError("Mismatch in DRBD kernel version and requested ganeti"
427
                      " usage: kernel is %s.%s, ganeti wants 8.x",
428
                      version["k_major"], version["k_minor"])
429

    
430
    if (self._lhost is not None and self._lhost == self._rhost and
431
            self._lport == self._rport):
432
      raise ValueError("Invalid configuration data, same local/remote %s" %
433
                       (unique_id,))
434
    self.Attach()
435

    
436
  @staticmethod
437
  def GetUsermodeHelper(filename=_USERMODE_HELPER_FILE):
438
    """Returns DRBD usermode_helper currently set.
439

440
    """
441
    try:
442
      helper = utils.ReadFile(filename).splitlines()[0]
443
    except EnvironmentError, err:
444
      if err.errno == errno.ENOENT:
445
        base.ThrowError("The file %s cannot be opened, check if the module"
446
                        " is loaded (%s)", filename, str(err))
447
      else:
448
        base.ThrowError("Can't read DRBD helper file %s: %s",
449
                        filename, str(err))
450
    if not helper:
451
      base.ThrowError("Can't read any data from %s", filename)
452
    return helper
453

    
454
  @staticmethod
455
  def _DevPath(minor):
456
    """Return the path to a drbd device for a given minor.
457

458
    """
459
    return "/dev/drbd%d" % minor
460

    
461
  @classmethod
462
  def GetUsedDevs(cls):
463
    """Compute the list of used DRBD devices.
464

465
    """
466
    drbd_info = DRBD8Info.CreateFromFile()
467
    return filter(lambda m: not drbd_info.GetMinorStatus(m).is_unconfigured,
468
                  drbd_info.GetMinors())
469

    
470
  def _SetFromMinor(self, minor):
471
    """Set our parameters based on the given minor.
472

473
    This sets our minor variable and our dev_path.
474

475
    """
476
    if minor is None:
477
      self.minor = self.dev_path = None
478
      self.attached = False
479
    else:
480
      self.minor = minor
481
      self.dev_path = self._DevPath(minor)
482
      self.attached = True
483

    
484
  @staticmethod
485
  def _CheckMetaSize(meta_device):
486
    """Check if the given meta device looks like a valid one.
487

488
    This currently only checks the size, which must be around
489
    128MiB.
490

491
    """
492
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
493
    if result.failed:
494
      base.ThrowError("Failed to get device size: %s - %s",
495
                      result.fail_reason, result.output)
496
    try:
497
      sectors = int(result.stdout)
498
    except (TypeError, ValueError):
499
      base.ThrowError("Invalid output from blockdev: '%s'", result.stdout)
500
    num_bytes = sectors * 512
501
    if num_bytes < 128 * 1024 * 1024: # less than 128MiB
502
      base.ThrowError("Meta device too small (%.2fMib)",
503
                      (num_bytes / 1024 / 1024))
504
    # the maximum *valid* size of the meta device when living on top
505
    # of LVM is hard to compute: it depends on the number of stripes
506
    # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
507
    # (normal size), but an eight-stripe 128MB PE will result in a 1GB
508
    # size meta device; as such, we restrict it to 1GB (a little bit
509
    # too generous, but making assumptions about PE size is hard)
510
    if num_bytes > 1024 * 1024 * 1024:
511
      base.ThrowError("Meta device too big (%.2fMiB)",
512
                      (num_bytes / 1024 / 1024))
513

    
514
  @classmethod
515
  def _InitMeta(cls, minor, dev_path):
516
    """Initialize a meta device.
517

518
    This will not work if the given minor is in use.
519

520
    """
521
    # Zero the metadata first, in order to make sure drbdmeta doesn't
522
    # try to auto-detect existing filesystems or similar (see
523
    # http://code.google.com/p/ganeti/issues/detail?id=182); we only
524
    # care about the first 128MB of data in the device, even though it
525
    # can be bigger
526
    result = utils.RunCmd([constants.DD_CMD,
527
                           "if=/dev/zero", "of=%s" % dev_path,
528
                           "bs=1048576", "count=128", "oflag=direct"])
529
    if result.failed:
530
      base.ThrowError("Can't wipe the meta device: %s", result.output)
531

    
532
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
533
                           "v08", dev_path, "0", "create-md"])
534
    if result.failed:
535
      base.ThrowError("Can't initialize meta device: %s", result.output)
536

    
537
  def _FindUnusedMinor(self):
538
    """Find an unused DRBD device.
539

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

543
    """
544

    
545
    highest = None
546
    drbd_info = DRBD8Info.CreateFromFile()
547
    for minor in drbd_info.GetMinors():
548
      status = drbd_info.GetMinorStatus(minor)
549
      if not status.is_in_use:
550
        return minor
551
      highest = max(highest, minor)
552

    
553
    if highest is None: # there are no minors in use at all
554
      return 0
555
    if highest >= self._MAX_MINORS:
556
      logging.error("Error: no free drbd minors!")
557
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
558

    
559
    return highest + 1
560

    
561
  @classmethod
562
  def _GetShowData(cls, minor):
563
    """Return the `drbdsetup show` data for a minor.
564

565
    """
566
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
567
    if result.failed:
568
      logging.error("Can't display the drbd config: %s - %s",
569
                    result.fail_reason, result.output)
570
      return None
571
    return result.stdout
572

    
573
  def _MatchesLocal(self, info):
574
    """Test if our local config matches with an existing device.
575

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

581
    """
582
    if self._children:
583
      backend, meta = self._children
584
    else:
585
      backend = meta = None
586

    
587
    if backend is not None:
588
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
589
    else:
590
      retval = ("local_dev" not in info)
591

    
592
    if meta is not None:
593
      retval = retval and ("meta_dev" in info and
594
                           info["meta_dev"] == meta.dev_path)
595
      retval = retval and ("meta_index" in info and
596
                           info["meta_index"] == 0)
597
    else:
598
      retval = retval and ("meta_dev" not in info and
599
                           "meta_index" not in info)
600
    return retval
601

    
602
  def _MatchesNet(self, info):
603
    """Test if our network config matches with an existing device.
604

605
    The parameter should be as returned from `_GetDevInfo()`. This
606
    method tests if our network configuration is the same as the one
607
    in the info parameter, in effect testing if we look like the given
608
    device.
609

610
    """
611
    if (((self._lhost is None and not ("local_addr" in info)) and
612
         (self._rhost is None and not ("remote_addr" in info)))):
613
      return True
614

    
615
    if self._lhost is None:
616
      return False
617

    
618
    if not ("local_addr" in info and
619
            "remote_addr" in info):
620
      return False
621

    
622
    retval = (info["local_addr"] == (self._lhost, self._lport))
623
    retval = (retval and
624
              info["remote_addr"] == (self._rhost, self._rport))
625
    return retval
626

    
627
  def _AssembleLocal(self, minor, backend, meta, size):
628
    """Configure the local part of a DRBD device.
629

630
    """
631
    args = ["drbdsetup", self._DevPath(minor), "disk",
632
            backend, meta, "0",
633
            "-e", "detach",
634
            "--create-device"]
635
    if size:
636
      args.extend(["-d", "%sm" % size])
637

    
638
    drbd_info = DRBD8Info.CreateFromFile()
639
    version = drbd_info.GetVersion()
640
    vmaj = version["k_major"]
641
    vmin = version["k_minor"]
642
    vrel = version["k_point"]
643

    
644
    barrier_args = \
645
      self._ComputeDiskBarrierArgs(vmaj, vmin, vrel,
646
                                   self.params[constants.LDP_BARRIERS],
647
                                   self.params[constants.LDP_NO_META_FLUSH])
648
    args.extend(barrier_args)
649

    
650
    if self.params[constants.LDP_DISK_CUSTOM]:
651
      args.extend(shlex.split(self.params[constants.LDP_DISK_CUSTOM]))
652

    
653
    result = utils.RunCmd(args)
654
    if result.failed:
655
      base.ThrowError("drbd%d: can't attach local disk: %s",
656
                      minor, result.output)
657

    
658
  @classmethod
659
  def _ComputeDiskBarrierArgs(cls, vmaj, vmin, vrel, disabled_barriers,
660
                              disable_meta_flush):
661
    """Compute the DRBD command line parameters for disk barriers
662

663
    Returns a list of the disk barrier parameters as requested via the
664
    disabled_barriers and disable_meta_flush arguments, and according to the
665
    supported ones in the DRBD version vmaj.vmin.vrel
666

667
    If the desired option is unsupported, raises errors.BlockDeviceError.
668

669
    """
670
    disabled_barriers_set = frozenset(disabled_barriers)
671
    if not disabled_barriers_set in constants.DRBD_VALID_BARRIER_OPT:
672
      raise errors.BlockDeviceError("%s is not a valid option set for DRBD"
673
                                    " barriers" % disabled_barriers)
674

    
675
    args = []
676

    
677
    # The following code assumes DRBD 8.x, with x < 4 and x != 1 (DRBD 8.1.x
678
    # does not exist)
679
    if not vmaj == 8 and vmin in (0, 2, 3):
680
      raise errors.BlockDeviceError("Unsupported DRBD version: %d.%d.%d" %
681
                                    (vmaj, vmin, vrel))
682

    
683
    def _AppendOrRaise(option, min_version):
684
      """Helper for DRBD options"""
685
      if min_version is not None and vrel >= min_version:
686
        args.append(option)
687
      else:
688
        raise errors.BlockDeviceError("Could not use the option %s as the"
689
                                      " DRBD version %d.%d.%d does not support"
690
                                      " it." % (option, vmaj, vmin, vrel))
691

    
692
    # the minimum version for each feature is encoded via pairs of (minor
693
    # version -> x) where x is version in which support for the option was
694
    # introduced.
695
    meta_flush_supported = disk_flush_supported = {
696
      0: 12,
697
      2: 7,
698
      3: 0,
699
      }
700

    
701
    disk_drain_supported = {
702
      2: 7,
703
      3: 0,
704
      }
705

    
706
    disk_barriers_supported = {
707
      3: 0,
708
      }
709

    
710
    # meta flushes
711
    if disable_meta_flush:
712
      _AppendOrRaise(cls._DISABLE_META_FLUSH_OPTION,
713
                     meta_flush_supported.get(vmin, None))
714

    
715
    # disk flushes
716
    if constants.DRBD_B_DISK_FLUSH in disabled_barriers_set:
717
      _AppendOrRaise(cls._DISABLE_FLUSH_OPTION,
718
                     disk_flush_supported.get(vmin, None))
719

    
720
    # disk drain
721
    if constants.DRBD_B_DISK_DRAIN in disabled_barriers_set:
722
      _AppendOrRaise(cls._DISABLE_DRAIN_OPTION,
723
                     disk_drain_supported.get(vmin, None))
724

    
725
    # disk barriers
726
    if constants.DRBD_B_DISK_BARRIERS in disabled_barriers_set:
727
      _AppendOrRaise(cls._DISABLE_DISK_OPTION,
728
                     disk_barriers_supported.get(vmin, None))
729

    
730
    return args
731

    
732
  def _AssembleNet(self, minor, net_info, protocol,
733
                   dual_pri=False, hmac=None, secret=None):
734
    """Configure the network part of the device.
735

736
    """
737
    lhost, lport, rhost, rport = net_info
738
    if None in net_info:
739
      # we don't want network connection and actually want to make
740
      # sure its shutdown
741
      self._ShutdownNet(minor)
742
      return
743

    
744
    # Workaround for a race condition. When DRBD is doing its dance to
745
    # establish a connection with its peer, it also sends the
746
    # synchronization speed over the wire. In some cases setting the
747
    # sync speed only after setting up both sides can race with DRBD
748
    # connecting, hence we set it here before telling DRBD anything
749
    # about its peer.
750
    sync_errors = self._SetMinorSyncParams(minor, self.params)
751
    if sync_errors:
752
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
753
                      (minor, utils.CommaJoin(sync_errors)))
754

    
755
    if netutils.IP6Address.IsValid(lhost):
756
      if not netutils.IP6Address.IsValid(rhost):
757
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
758
                        (minor, lhost, rhost))
759
      family = "ipv6"
760
    elif netutils.IP4Address.IsValid(lhost):
761
      if not netutils.IP4Address.IsValid(rhost):
762
        base.ThrowError("drbd%d: can't connect ip %s to ip %s" %
763
                        (minor, lhost, rhost))
764
      family = "ipv4"
765
    else:
766
      base.ThrowError("drbd%d: Invalid ip %s" % (minor, lhost))
767

    
768
    args = ["drbdsetup", self._DevPath(minor), "net",
769
            "%s:%s:%s" % (family, lhost, lport),
770
            "%s:%s:%s" % (family, rhost, rport), protocol,
771
            "-A", "discard-zero-changes",
772
            "-B", "consensus",
773
            "--create-device",
774
            ]
775
    if dual_pri:
776
      args.append("-m")
777
    if hmac and secret:
778
      args.extend(["-a", hmac, "-x", secret])
779

    
780
    if self.params[constants.LDP_NET_CUSTOM]:
781
      args.extend(shlex.split(self.params[constants.LDP_NET_CUSTOM]))
782

    
783
    result = utils.RunCmd(args)
784
    if result.failed:
785
      base.ThrowError("drbd%d: can't setup network: %s - %s",
786
                      minor, result.fail_reason, result.output)
787

    
788
    def _CheckNetworkConfig():
789
      info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))
790
      if not "local_addr" in info or not "remote_addr" in info:
791
        raise utils.RetryAgain()
792

    
793
      if (info["local_addr"] != (lhost, lport) or
794
          info["remote_addr"] != (rhost, rport)):
795
        raise utils.RetryAgain()
796

    
797
    try:
798
      utils.Retry(_CheckNetworkConfig, 1.0, 10.0)
799
    except utils.RetryTimeout:
800
      base.ThrowError("drbd%d: timeout while configuring network", minor)
801

    
802
  def AddChildren(self, devices):
803
    """Add a disk to the DRBD device.
804

805
    """
806
    if self.minor is None:
807
      base.ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
808
                      self._aminor)
809
    if len(devices) != 2:
810
      base.ThrowError("drbd%d: need two devices for AddChildren", self.minor)
811
    info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(self.minor))
812
    if "local_dev" in info:
813
      base.ThrowError("drbd%d: already attached to a local disk", self.minor)
814
    backend, meta = devices
815
    if backend.dev_path is None or meta.dev_path is None:
816
      base.ThrowError("drbd%d: children not ready during AddChildren",
817
                      self.minor)
818
    backend.Open()
819
    meta.Open()
820
    self._CheckMetaSize(meta.dev_path)
821
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
822

    
823
    self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
824
    self._children = devices
825

    
826
  def RemoveChildren(self, devices):
827
    """Detach the drbd device from local storage.
828

829
    """
830
    if self.minor is None:
831
      base.ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
832
                      self._aminor)
833
    # early return if we don't actually have backing storage
834
    info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(self.minor))
835
    if "local_dev" not in info:
836
      return
837
    if len(self._children) != 2:
838
      base.ThrowError("drbd%d: we don't have two children: %s", self.minor,
839
                      self._children)
840
    if self._children.count(None) == 2: # we don't actually have children :)
841
      logging.warning("drbd%d: requested detach while detached", self.minor)
842
      return
843
    if len(devices) != 2:
844
      base.ThrowError("drbd%d: we need two children in RemoveChildren",
845
                      self.minor)
846
    for child, dev in zip(self._children, devices):
847
      if dev != child.dev_path:
848
        base.ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
849
                        " RemoveChildren", self.minor, dev, child.dev_path)
850

    
851
    self._ShutdownLocal(self.minor)
852
    self._children = []
853

    
854
  def _SetMinorSyncParams(self, minor, params):
855
    """Set the parameters of the DRBD syncer.
856

857
    This is the low-level implementation.
858

859
    @type minor: int
860
    @param minor: the drbd minor whose settings we change
861
    @type params: dict
862
    @param params: LD level disk parameters related to the synchronization
863
    @rtype: list
864
    @return: a list of error messages
865

866
    """
867

    
868
    args = ["drbdsetup", self._DevPath(minor), "syncer"]
869
    if params[constants.LDP_DYNAMIC_RESYNC]:
870
      drbd_info = DRBD8Info.CreateFromFile()
871
      version = drbd_info.GetVersion()
872
      vmin = version["k_minor"]
873
      vrel = version["k_point"]
874

    
875
      # By definition we are using 8.x, so just check the rest of the version
876
      # number
877
      if vmin != 3 or vrel < 9:
878
        msg = ("The current DRBD version (8.%d.%d) does not support the "
879
               "dynamic resync speed controller" % (vmin, vrel))
880
        logging.error(msg)
881
        return [msg]
882

    
883
      if params[constants.LDP_PLAN_AHEAD] == 0:
884
        msg = ("A value of 0 for c-plan-ahead disables the dynamic sync speed"
885
               " controller at DRBD level. If you want to disable it, please"
886
               " set the dynamic-resync disk parameter to False.")
887
        logging.error(msg)
888
        return [msg]
889

    
890
      # add the c-* parameters to args
891
      args.extend(["--c-plan-ahead", params[constants.LDP_PLAN_AHEAD],
892
                   "--c-fill-target", params[constants.LDP_FILL_TARGET],
893
                   "--c-delay-target", params[constants.LDP_DELAY_TARGET],
894
                   "--c-max-rate", params[constants.LDP_MAX_RATE],
895
                   "--c-min-rate", params[constants.LDP_MIN_RATE],
896
                   ])
897

    
898
    else:
899
      args.extend(["-r", "%d" % params[constants.LDP_RESYNC_RATE]])
900

    
901
    args.append("--create-device")
902
    result = utils.RunCmd(args)
903
    if result.failed:
904
      msg = ("Can't change syncer rate: %s - %s" %
905
             (result.fail_reason, result.output))
906
      logging.error(msg)
907
      return [msg]
908

    
909
    return []
910

    
911
  def SetSyncParams(self, params):
912
    """Set the synchronization parameters of the DRBD syncer.
913

914
    @type params: dict
915
    @param params: LD level disk parameters related to the synchronization
916
    @rtype: list
917
    @return: a list of error messages, emitted both by the current node and by
918
    children. An empty list means no errors
919

920
    """
921
    if self.minor is None:
922
      err = "Not attached during SetSyncParams"
923
      logging.info(err)
924
      return [err]
925

    
926
    children_result = super(DRBD8, self).SetSyncParams(params)
927
    children_result.extend(self._SetMinorSyncParams(self.minor, params))
928
    return children_result
929

    
930
  def PauseResumeSync(self, pause):
931
    """Pauses or resumes the sync of a DRBD device.
932

933
    @param pause: Wether to pause or resume
934
    @return: the success of the operation
935

936
    """
937
    if self.minor is None:
938
      logging.info("Not attached during PauseSync")
939
      return False
940

    
941
    children_result = super(DRBD8, self).PauseResumeSync(pause)
942

    
943
    if pause:
944
      cmd = "pause-sync"
945
    else:
946
      cmd = "resume-sync"
947

    
948
    result = utils.RunCmd(["drbdsetup", self.dev_path, cmd])
949
    if result.failed:
950
      logging.error("Can't %s: %s - %s", cmd,
951
                    result.fail_reason, result.output)
952
    return not result.failed and children_result
953

    
954
  def GetProcStatus(self):
955
    """Return device data from /proc.
956

957
    """
958
    if self.minor is None:
959
      base.ThrowError("drbd%d: GetStats() called while not attached",
960
                      self._aminor)
961
    drbd_info = DRBD8Info.CreateFromFile()
962
    if not drbd_info.HasMinorStatus(self.minor):
963
      base.ThrowError("drbd%d: can't find myself in /proc", self.minor)
964
    return drbd_info.GetMinorStatus(self.minor)
965

    
966
  def GetSyncStatus(self):
967
    """Returns the sync status of the device.
968

969

970
    If sync_percent is None, it means all is ok
971
    If estimated_time is None, it means we can't estimate
972
    the time needed, otherwise it's the time left in seconds.
973

974

975
    We set the is_degraded parameter to True on two conditions:
976
    network not connected or local disk missing.
977

978
    We compute the ldisk parameter based on whether we have a local
979
    disk or not.
980

981
    @rtype: objects.BlockDevStatus
982

983
    """
984
    if self.minor is None and not self.Attach():
985
      base.ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
986

    
987
    stats = self.GetProcStatus()
988
    is_degraded = not stats.is_connected or not stats.is_disk_uptodate
989

    
990
    if stats.is_disk_uptodate:
991
      ldisk_status = constants.LDS_OKAY
992
    elif stats.is_diskless:
993
      ldisk_status = constants.LDS_FAULTY
994
    else:
995
      ldisk_status = constants.LDS_UNKNOWN
996

    
997
    return objects.BlockDevStatus(dev_path=self.dev_path,
998
                                  major=self.major,
999
                                  minor=self.minor,
1000
                                  sync_percent=stats.sync_percent,
1001
                                  estimated_time=stats.est_time,
1002
                                  is_degraded=is_degraded,
1003
                                  ldisk_status=ldisk_status)
1004

    
1005
  def Open(self, force=False):
1006
    """Make the local state primary.
1007

1008
    If the 'force' parameter is given, the '-o' option is passed to
1009
    drbdsetup. Since this is a potentially dangerous operation, the
1010
    force flag should be only given after creation, when it actually
1011
    is mandatory.
1012

1013
    """
1014
    if self.minor is None and not self.Attach():
1015
      logging.error("DRBD cannot attach to a device during open")
1016
      return False
1017
    cmd = ["drbdsetup", self.dev_path, "primary"]
1018
    if force:
1019
      cmd.append("-o")
1020
    result = utils.RunCmd(cmd)
1021
    if result.failed:
1022
      base.ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1023
                      result.output)
1024

    
1025
  def Close(self):
1026
    """Make the local state secondary.
1027

1028
    This will, of course, fail if the device is in use.
1029

1030
    """
1031
    if self.minor is None and not self.Attach():
1032
      base.ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1033
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1034
    if result.failed:
1035
      base.ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1036
                      self.minor, result.output)
1037

    
1038
  def DisconnectNet(self):
1039
    """Removes network configuration.
1040

1041
    This method shutdowns the network side of the device.
1042

1043
    The method will wait up to a hardcoded timeout for the device to
1044
    go into standalone after the 'disconnect' command before
1045
    re-configuring it, as sometimes it takes a while for the
1046
    disconnect to actually propagate and thus we might issue a 'net'
1047
    command while the device is still connected. If the device will
1048
    still be attached to the network and we time out, we raise an
1049
    exception.
1050

1051
    """
1052
    if self.minor is None:
1053
      base.ThrowError("drbd%d: disk not attached in re-attach net",
1054
                      self._aminor)
1055

    
1056
    if None in (self._lhost, self._lport, self._rhost, self._rport):
1057
      base.ThrowError("drbd%d: DRBD disk missing network info in"
1058
                      " DisconnectNet()", self.minor)
1059

    
1060
    class _DisconnectStatus:
1061
      def __init__(self, ever_disconnected):
1062
        self.ever_disconnected = ever_disconnected
1063

    
1064
    dstatus = _DisconnectStatus(base.IgnoreError(self._ShutdownNet, self.minor))
1065

    
1066
    def _WaitForDisconnect():
1067
      if self.GetProcStatus().is_standalone:
1068
        return
1069

    
1070
      # retry the disconnect, it seems possible that due to a well-time
1071
      # disconnect on the peer, my disconnect command might be ignored and
1072
      # forgotten
1073
      dstatus.ever_disconnected = \
1074
        base.IgnoreError(self._ShutdownNet, self.minor) or \
1075
        dstatus.ever_disconnected
1076

    
1077
      raise utils.RetryAgain()
1078

    
1079
    # Keep start time
1080
    start_time = time.time()
1081

    
1082
    try:
1083
      # Start delay at 100 milliseconds and grow up to 2 seconds
1084
      utils.Retry(_WaitForDisconnect, (0.1, 1.5, 2.0),
1085
                  self._NET_RECONFIG_TIMEOUT)
1086
    except utils.RetryTimeout:
1087
      if dstatus.ever_disconnected:
1088
        msg = ("drbd%d: device did not react to the"
1089
               " 'disconnect' command in a timely manner")
1090
      else:
1091
        msg = "drbd%d: can't shutdown network, even after multiple retries"
1092

    
1093
      base.ThrowError(msg, self.minor)
1094

    
1095
    reconfig_time = time.time() - start_time
1096
    if reconfig_time > (self._NET_RECONFIG_TIMEOUT * 0.25):
1097
      logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1098
                   self.minor, reconfig_time)
1099

    
1100
  def AttachNet(self, multimaster):
1101
    """Reconnects the network.
1102

1103
    This method connects the network side of the device with a
1104
    specified multi-master flag. The device needs to be 'Standalone'
1105
    but have valid network configuration data.
1106

1107
    Args:
1108
      - multimaster: init the network in dual-primary mode
1109

1110
    """
1111
    if self.minor is None:
1112
      base.ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1113

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

    
1117
    status = self.GetProcStatus()
1118

    
1119
    if not status.is_standalone:
1120
      base.ThrowError("drbd%d: device is not standalone in AttachNet",
1121
                      self.minor)
1122

    
1123
    self._AssembleNet(self.minor,
1124
                      (self._lhost, self._lport, self._rhost, self._rport),
1125
                      constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1126
                      hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1127

    
1128
  def Attach(self):
1129
    """Check if our minor is configured.
1130

1131
    This doesn't do any device configurations - it only checks if the
1132
    minor is in a state different from Unconfigured.
1133

1134
    Note that this function will not change the state of the system in
1135
    any way (except in case of side-effects caused by reading from
1136
    /proc).
1137

1138
    """
1139
    used_devs = self.GetUsedDevs()
1140
    if self._aminor in used_devs:
1141
      minor = self._aminor
1142
    else:
1143
      minor = None
1144

    
1145
    self._SetFromMinor(minor)
1146
    return minor is not None
1147

    
1148
  def Assemble(self):
1149
    """Assemble the drbd.
1150

1151
    Method:
1152
      - if we have a configured device, we try to ensure that it matches
1153
        our config
1154
      - if not, we create it from zero
1155
      - anyway, set the device parameters
1156

1157
    """
1158
    super(DRBD8, self).Assemble()
1159

    
1160
    self.Attach()
1161
    if self.minor is None:
1162
      # local device completely unconfigured
1163
      self._FastAssemble()
1164
    else:
1165
      # we have to recheck the local and network status and try to fix
1166
      # the device
1167
      self._SlowAssemble()
1168

    
1169
    sync_errors = self.SetSyncParams(self.params)
1170
    if sync_errors:
1171
      base.ThrowError("drbd%d: can't set the synchronization parameters: %s" %
1172
                      (self.minor, utils.CommaJoin(sync_errors)))
1173

    
1174
  def _SlowAssemble(self):
1175
    """Assembles the DRBD device from a (partially) configured device.
1176

1177
    In case of partially attached (local device matches but no network
1178
    setup), we perform the network attach. If successful, we re-test
1179
    the attach if can return success.
1180

1181
    """
1182
    # TODO: Rewrite to not use a for loop just because there is 'break'
1183
    # pylint: disable=W0631
1184
    net_data = (self._lhost, self._lport, self._rhost, self._rport)
1185
    for minor in (self._aminor,):
1186
      info = DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))
1187
      match_l = self._MatchesLocal(info)
1188
      match_r = self._MatchesNet(info)
1189

    
1190
      if match_l and match_r:
1191
        # everything matches
1192
        break
1193

    
1194
      if match_l and not match_r and "local_addr" not in info:
1195
        # disk matches, but not attached to network, attach and recheck
1196
        self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1197
                          hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1198
        if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))):
1199
          break
1200
        else:
1201
          base.ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1202
                          " show' disagrees", minor)
1203

    
1204
      if match_r and "local_dev" not in info:
1205
        # no local disk, but network attached and it matches
1206
        self._AssembleLocal(minor, self._children[0].dev_path,
1207
                            self._children[1].dev_path, self.size)
1208
        if self._MatchesNet(DRBD8ShowInfo.GetDevInfo(self._GetShowData(minor))):
1209
          break
1210
        else:
1211
          base.ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1212
                          " show' disagrees", minor)
1213

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

    
1241
    else:
1242
      minor = None
1243

    
1244
    self._SetFromMinor(minor)
1245
    if minor is None:
1246
      base.ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1247
                      self._aminor)
1248

    
1249
  def _FastAssemble(self):
1250
    """Assemble the drbd device from zero.
1251

1252
    This is run when in Assemble we detect our minor is unused.
1253

1254
    """
1255
    minor = self._aminor
1256
    if self._children and self._children[0] and self._children[1]:
1257
      self._AssembleLocal(minor, self._children[0].dev_path,
1258
                          self._children[1].dev_path, self.size)
1259
    if self._lhost and self._lport and self._rhost and self._rport:
1260
      self._AssembleNet(minor,
1261
                        (self._lhost, self._lport, self._rhost, self._rport),
1262
                        constants.DRBD_NET_PROTOCOL,
1263
                        hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1264
    self._SetFromMinor(minor)
1265

    
1266
  @classmethod
1267
  def _ShutdownLocal(cls, minor):
1268
    """Detach from the local device.
1269

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

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

    
1279
  @classmethod
1280
  def _ShutdownNet(cls, minor):
1281
    """Disconnect from the remote peer.
1282

1283
    This fails if we don't have a local device.
1284

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

    
1291
  @classmethod
1292
  def _ShutdownAll(cls, minor):
1293
    """Deactivate the device.
1294

1295
    This will, of course, fail if the device is in use.
1296

1297
    """
1298
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1299
    if result.failed:
1300
      base.ThrowError("drbd%d: can't shutdown drbd device: %s",
1301
                      minor, result.output)
1302

    
1303
  def Shutdown(self):
1304
    """Shutdown the DRBD device.
1305

1306
    """
1307
    if self.minor is None and not self.Attach():
1308
      logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1309
      return
1310
    minor = self.minor
1311
    self.minor = None
1312
    self.dev_path = None
1313
    self._ShutdownAll(minor)
1314

    
1315
  def Remove(self):
1316
    """Stub remove for DRBD devices.
1317

1318
    """
1319
    self.Shutdown()
1320

    
1321
  def Rename(self, new_id):
1322
    """Rename a device.
1323

1324
    This is not supported for drbd devices.
1325

1326
    """
1327
    raise errors.ProgrammerError("Can't rename a drbd device")
1328

    
1329
  @classmethod
1330
  def Create(cls, unique_id, children, size, params, excl_stor):
1331
    """Create a new DRBD8 device.
1332

1333
    Since DRBD devices are not created per se, just assembled, this
1334
    function only initializes the metadata.
1335

1336
    """
1337
    if len(children) != 2:
1338
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1339
    if excl_stor:
1340
      raise errors.ProgrammerError("DRBD device requested with"
1341
                                   " exclusive_storage")
1342
    # check that the minor is unused
1343
    aminor = unique_id[4]
1344

    
1345
    drbd_info = DRBD8Info.CreateFromFile()
1346
    if drbd_info.HasMinorStatus(aminor):
1347
      status = drbd_info.GetMinorStatus(aminor)
1348
      in_use = status.is_in_use
1349
    else:
1350
      in_use = False
1351
    if in_use:
1352
      base.ThrowError("drbd%d: minor is already in use at Create() time",
1353
                      aminor)
1354
    meta = children[1]
1355
    meta.Assemble()
1356
    if not meta.Attach():
1357
      base.ThrowError("drbd%d: can't attach to meta device '%s'",
1358
                      aminor, meta)
1359
    cls._CheckMetaSize(meta.dev_path)
1360
    cls._InitMeta(aminor, meta.dev_path)
1361
    return cls(unique_id, children, size, params)
1362

    
1363
  def Grow(self, amount, dryrun, backingstore):
1364
    """Resize the DRBD device and its backing storage.
1365

1366
    """
1367
    if self.minor is None:
1368
      base.ThrowError("drbd%d: Grow called while not attached", self._aminor)
1369
    if len(self._children) != 2 or None in self._children:
1370
      base.ThrowError("drbd%d: cannot grow diskless device", self.minor)
1371
    self._children[0].Grow(amount, dryrun, backingstore)
1372
    if dryrun or backingstore:
1373
      # DRBD does not support dry-run mode and is not backing storage,
1374
      # so we'll return here
1375
      return
1376
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize", "-s",
1377
                           "%dm" % (self.size + amount)])
1378
    if result.failed:
1379
      base.ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1380

    
1381

    
1382
def _CanReadDevice(path):
1383
  """Check if we can read from the given device.
1384

1385
  This tries to read the first 128k of the device.
1386

1387
  """
1388
  try:
1389
    utils.ReadFile(path, size=_DEVICE_READ_SIZE)
1390
    return True
1391
  except EnvironmentError:
1392
    logging.warning("Can't read from device %s", path, exc_info=True)
1393
    return False