Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ 6b90c22e

History | View | Annotate | Download (49.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007 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
"""Block device abstraction"""
23

    
24
import re
25
import time
26
import errno
27
import pyparsing as pyp
28
import os
29

    
30
from ganeti import utils
31
from ganeti import logger
32
from ganeti import errors
33
from ganeti import constants
34

    
35

    
36
class BlockDev(object):
37
  """Block device abstract class.
38

39
  A block device can be in the following states:
40
    - not existing on the system, and by `Create()` it goes into:
41
    - existing but not setup/not active, and by `Assemble()` goes into:
42
    - active read-write and by `Open()` it goes into
43
    - online (=used, or ready for use)
44

45
  A device can also be online but read-only, however we are not using
46
  the readonly state (LV has it, if needed in the future) and we are
47
  usually looking at this like at a stack, so it's easier to
48
  conceptualise the transition from not-existing to online and back
49
  like a linear one.
50

51
  The many different states of the device are due to the fact that we
52
  need to cover many device types:
53
    - logical volumes are created, lvchange -a y $lv, and used
54
    - drbd devices are attached to a local disk/remote peer and made primary
55

56
  A block device is identified by three items:
57
    - the /dev path of the device (dynamic)
58
    - a unique ID of the device (static)
59
    - it's major/minor pair (dynamic)
60

61
  Not all devices implement both the first two as distinct items. LVM
62
  logical volumes have their unique ID (the pair volume group, logical
63
  volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
64
  the /dev path is again dynamic and the unique id is the pair (host1,
65
  dev1), (host2, dev2).
66

67
  You can get to a device in two ways:
68
    - creating the (real) device, which returns you
69
      an attached instance (lvcreate)
70
    - attaching of a python instance to an existing (real) device
71

72
  The second point, the attachement to a device, is different
73
  depending on whether the device is assembled or not. At init() time,
74
  we search for a device with the same unique_id as us. If found,
75
  good. It also means that the device is already assembled. If not,
76
  after assembly we'll have our correct major/minor.
77

78
  """
79
  def __init__(self, unique_id, children):
80
    self._children = children
81
    self.dev_path = None
82
    self.unique_id = unique_id
83
    self.major = None
84
    self.minor = None
85

    
86
  def Assemble(self):
87
    """Assemble the device from its components.
88

89
    If this is a plain block device (e.g. LVM) than assemble does
90
    nothing, as the LVM has no children and we don't put logical
91
    volumes offline.
92

93
    One guarantee is that after the device has been assembled, it
94
    knows its major/minor numbers. This allows other devices (usually
95
    parents) to probe correctly for their children.
96

97
    """
98
    status = True
99
    for child in self._children:
100
      if not isinstance(child, BlockDev):
101
        raise TypeError("Invalid child passed of type '%s'" % type(child))
102
      if not status:
103
        break
104
      status = status and child.Assemble()
105
      if not status:
106
        break
107

    
108
      try:
109
        child.Open()
110
      except errors.BlockDeviceError:
111
        for child in self._children:
112
          child.Shutdown()
113
        raise
114

    
115
    if not status:
116
      for child in self._children:
117
        child.Shutdown()
118
    return status
119

    
120
  def Attach(self):
121
    """Find a device which matches our config and attach to it.
122

123
    """
124
    raise NotImplementedError
125

    
126
  def Close(self):
127
    """Notifies that the device will no longer be used for I/O.
128

129
    """
130
    raise NotImplementedError
131

    
132
  @classmethod
133
  def Create(cls, unique_id, children, size):
134
    """Create the device.
135

136
    If the device cannot be created, it will return None
137
    instead. Error messages go to the logging system.
138

139
    Note that for some devices, the unique_id is used, and for other,
140
    the children. The idea is that these two, taken together, are
141
    enough for both creation and assembly (later).
142

143
    """
144
    raise NotImplementedError
145

    
146
  def Remove(self):
147
    """Remove this device.
148

149
    This makes sense only for some of the device types: LV and file
150
    storeage. Also note that if the device can't attach, the removal
151
    can't be completed.
152

153
    """
154
    raise NotImplementedError
155

    
156
  def Rename(self, new_id):
157
    """Rename this device.
158

159
    This may or may not make sense for a given device type.
160

161
    """
162
    raise NotImplementedError
163

    
164
  def Open(self, force=False):
165
    """Make the device ready for use.
166

167
    This makes the device ready for I/O. For now, just the DRBD
168
    devices need this.
169

170
    The force parameter signifies that if the device has any kind of
171
    --force thing, it should be used, we know what we are doing.
172

173
    """
174
    raise NotImplementedError
175

    
176
  def Shutdown(self):
177
    """Shut down the device, freeing its children.
178

179
    This undoes the `Assemble()` work, except for the child
180
    assembling; as such, the children on the device are still
181
    assembled after this call.
182

183
    """
184
    raise NotImplementedError
185

    
186
  def SetSyncSpeed(self, speed):
187
    """Adjust the sync speed of the mirror.
188

189
    In case this is not a mirroring device, this is no-op.
190

191
    """
192
    result = True
193
    if self._children:
194
      for child in self._children:
195
        result = result and child.SetSyncSpeed(speed)
196
    return result
197

    
198
  def GetSyncStatus(self):
199
    """Returns the sync status of the device.
200

201
    If this device is a mirroring device, this function returns the
202
    status of the mirror.
203

204
    Returns:
205
     (sync_percent, estimated_time, is_degraded, ldisk)
206

207
    If sync_percent is None, it means the device is not syncing.
208

209
    If estimated_time is None, it means we can't estimate
210
    the time needed, otherwise it's the time left in seconds.
211

212
    If is_degraded is True, it means the device is missing
213
    redundancy. This is usually a sign that something went wrong in
214
    the device setup, if sync_percent is None.
215

216
    The ldisk parameter represents the degradation of the local
217
    data. This is only valid for some devices, the rest will always
218
    return False (not degraded).
219

220
    """
221
    return None, None, False, False
222

    
223

    
224
  def CombinedSyncStatus(self):
225
    """Calculate the mirror status recursively for our children.
226

227
    The return value is the same as for `GetSyncStatus()` except the
228
    minimum percent and maximum time are calculated across our
229
    children.
230

231
    """
232
    min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
233
    if self._children:
234
      for child in self._children:
235
        c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
236
        if min_percent is None:
237
          min_percent = c_percent
238
        elif c_percent is not None:
239
          min_percent = min(min_percent, c_percent)
240
        if max_time is None:
241
          max_time = c_time
242
        elif c_time is not None:
243
          max_time = max(max_time, c_time)
244
        is_degraded = is_degraded or c_degraded
245
        ldisk = ldisk or c_ldisk
246
    return min_percent, max_time, is_degraded, ldisk
247

    
248

    
249
  def SetInfo(self, text):
250
    """Update metadata with info text.
251

252
    Only supported for some device types.
253

254
    """
255
    for child in self._children:
256
      child.SetInfo(text)
257

    
258
  def Grow(self, amount):
259
    """Grow the block device.
260

261
    Arguments:
262
      amount: the amount (in mebibytes) to grow with
263

264
    Returns: None
265

266
    """
267
    raise NotImplementedError
268

    
269
  def __repr__(self):
270
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
271
            (self.__class__, self.unique_id, self._children,
272
             self.major, self.minor, self.dev_path))
273

    
274

    
275
class LogicalVolume(BlockDev):
276
  """Logical Volume block device.
277

278
  """
279
  def __init__(self, unique_id, children):
280
    """Attaches to a LV device.
281

282
    The unique_id is a tuple (vg_name, lv_name)
283

284
    """
285
    super(LogicalVolume, self).__init__(unique_id, children)
286
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
287
      raise ValueError("Invalid configuration data %s" % str(unique_id))
288
    self._vg_name, self._lv_name = unique_id
289
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
290
    self.Attach()
291

    
292
  @classmethod
293
  def Create(cls, unique_id, children, size):
294
    """Create a new logical volume.
295

296
    """
297
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
298
      raise ValueError("Invalid configuration data %s" % str(unique_id))
299
    vg_name, lv_name = unique_id
300
    pvs_info = cls.GetPVInfo(vg_name)
301
    if not pvs_info:
302
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
303
                                    vg_name)
304
    pvs_info.sort()
305
    pvs_info.reverse()
306

    
307
    pvlist = [ pv[1] for pv in pvs_info ]
308
    free_size = sum([ pv[0] for pv in pvs_info ])
309

    
310
    # The size constraint should have been checked from the master before
311
    # calling the create function.
312
    if free_size < size:
313
      raise errors.BlockDeviceError("Not enough free space: required %s,"
314
                                    " available %s" % (size, free_size))
315
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
316
                           vg_name] + pvlist)
317
    if result.failed:
318
      raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
319
                                                result.output))
320
    return LogicalVolume(unique_id, children)
321

    
322
  @staticmethod
323
  def GetPVInfo(vg_name):
324
    """Get the free space info for PVs in a volume group.
325

326
    Args:
327
      vg_name: the volume group name
328

329
    Returns:
330
      list of (free_space, name) with free_space in mebibytes
331

332
    """
333
    command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
334
               "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
335
               "--separator=:"]
336
    result = utils.RunCmd(command)
337
    if result.failed:
338
      logger.Error("Can't get the PV information: %s - %s" %
339
                   (result.fail_reason, result.output))
340
      return None
341
    data = []
342
    for line in result.stdout.splitlines():
343
      fields = line.strip().split(':')
344
      if len(fields) != 4:
345
        logger.Error("Can't parse pvs output: line '%s'" % line)
346
        return None
347
      # skip over pvs from another vg or ones which are not allocatable
348
      if fields[1] != vg_name or fields[3][0] != 'a':
349
        continue
350
      data.append((float(fields[2]), fields[0]))
351

    
352
    return data
353

    
354
  def Remove(self):
355
    """Remove this logical volume.
356

357
    """
358
    if not self.minor and not self.Attach():
359
      # the LV does not exist
360
      return True
361
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
362
                           (self._vg_name, self._lv_name)])
363
    if result.failed:
364
      logger.Error("Can't lvremove: %s - %s" %
365
                   (result.fail_reason, result.output))
366

    
367
    return not result.failed
368

    
369
  def Rename(self, new_id):
370
    """Rename this logical volume.
371

372
    """
373
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
374
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
375
    new_vg, new_name = new_id
376
    if new_vg != self._vg_name:
377
      raise errors.ProgrammerError("Can't move a logical volume across"
378
                                   " volume groups (from %s to to %s)" %
379
                                   (self._vg_name, new_vg))
380
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
381
    if result.failed:
382
      raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
383
                                    result.output)
384
    self._lv_name = new_name
385
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
386

    
387
  def Attach(self):
388
    """Attach to an existing LV.
389

390
    This method will try to see if an existing and active LV exists
391
    which matches our name. If so, its major/minor will be
392
    recorded.
393

394
    """
395
    result = utils.RunCmd(["lvdisplay", self.dev_path])
396
    if result.failed:
397
      logger.Error("Can't find LV %s: %s, %s" %
398
                   (self.dev_path, result.fail_reason, result.output))
399
      return False
400
    match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
401
    for line in result.stdout.splitlines():
402
      match_result = match.match(line)
403
      if match_result:
404
        self.major = int(match_result.group(1))
405
        self.minor = int(match_result.group(2))
406
        return True
407
    return False
408

    
409
  def Assemble(self):
410
    """Assemble the device.
411

412
    We alway run `lvchange -ay` on the LV to ensure it's active before
413
    use, as there were cases when xenvg was not active after boot
414
    (also possibly after disk issues).
415

416
    """
417
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
418
    if result.failed:
419
      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
420
    return not result.failed
421

    
422
  def Shutdown(self):
423
    """Shutdown the device.
424

425
    This is a no-op for the LV device type, as we don't deactivate the
426
    volumes on shutdown.
427

428
    """
429
    return True
430

    
431
  def GetSyncStatus(self):
432
    """Returns the sync status of the device.
433

434
    If this device is a mirroring device, this function returns the
435
    status of the mirror.
436

437
    Returns:
438
     (sync_percent, estimated_time, is_degraded, ldisk)
439

440
    For logical volumes, sync_percent and estimated_time are always
441
    None (no recovery in progress, as we don't handle the mirrored LV
442
    case). The is_degraded parameter is the inverse of the ldisk
443
    parameter.
444

445
    For the ldisk parameter, we check if the logical volume has the
446
    'virtual' type, which means it's not backed by existing storage
447
    anymore (read from it return I/O error). This happens after a
448
    physical disk failure and subsequent 'vgreduce --removemissing' on
449
    the volume group.
450

451
    """
452
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
453
    if result.failed:
454
      logger.Error("Can't display lv: %s - %s" %
455
                   (result.fail_reason, result.output))
456
      return None, None, True, True
457
    out = result.stdout.strip()
458
    # format: type/permissions/alloc/fixed_minor/state/open
459
    if len(out) != 6:
460
      logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
461
      return None, None, True, True
462
    ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
463
                          # backing storage
464
    return None, None, ldisk, ldisk
465

    
466
  def Open(self, force=False):
467
    """Make the device ready for I/O.
468

469
    This is a no-op for the LV device type.
470

471
    """
472
    pass
473

    
474
  def Close(self):
475
    """Notifies that the device will no longer be used for I/O.
476

477
    This is a no-op for the LV device type.
478

479
    """
480
    pass
481

    
482
  def Snapshot(self, size):
483
    """Create a snapshot copy of an lvm block device.
484

485
    """
486
    snap_name = self._lv_name + ".snap"
487

    
488
    # remove existing snapshot if found
489
    snap = LogicalVolume((self._vg_name, snap_name), None)
490
    snap.Remove()
491

    
492
    pvs_info = self.GetPVInfo(self._vg_name)
493
    if not pvs_info:
494
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
495
                                    self._vg_name)
496
    pvs_info.sort()
497
    pvs_info.reverse()
498
    free_size, pv_name = pvs_info[0]
499
    if free_size < size:
500
      raise errors.BlockDeviceError("Not enough free space: required %s,"
501
                                    " available %s" % (size, free_size))
502

    
503
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
504
                           "-n%s" % snap_name, self.dev_path])
505
    if result.failed:
506
      raise errors.BlockDeviceError("command: %s error: %s - %s" %
507
                                    (result.cmd, result.fail_reason,
508
                                     result.output))
509

    
510
    return snap_name
511

    
512
  def SetInfo(self, text):
513
    """Update metadata with info text.
514

515
    """
516
    BlockDev.SetInfo(self, text)
517

    
518
    # Replace invalid characters
519
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
520
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
521

    
522
    # Only up to 128 characters are allowed
523
    text = text[:128]
524

    
525
    result = utils.RunCmd(["lvchange", "--addtag", text,
526
                           self.dev_path])
527
    if result.failed:
528
      raise errors.BlockDeviceError("Command: %s error: %s - %s" %
529
                                    (result.cmd, result.fail_reason,
530
                                     result.output))
531
  def Grow(self, amount):
532
    """Grow the logical volume.
533

534
    """
535
    # we try multiple algorithms since the 'best' ones might not have
536
    # space available in the right place, but later ones might (since
537
    # they have less constraints); also note that only recent LVM
538
    # supports 'cling'
539
    for alloc_policy in "contiguous", "cling", "normal":
540
      result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
541
                             "-L", "+%dm" % amount, self.dev_path])
542
      if not result.failed:
543
        return
544
    raise errors.BlockDeviceError("Can't grow LV %s: %s" %
545
                                  (self.dev_path, result.output))
546

    
547

    
548
class DRBD8Status(object):
549
  """A DRBD status representation class.
550

551
  Note that this doesn't support unconfigured devices (cs:Unconfigured).
552

553
  """
554
  LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+st:([^/]+)/(\S+)"
555
                       "\s+ds:([^/]+)/(\S+)\s+.*$")
556
  SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
557
                       "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
558

    
559
  def __init__(self, procline):
560
    m = self.LINE_RE.match(procline)
561
    if not m:
562
      raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
563
    self.cstatus = m.group(1)
564
    self.lrole = m.group(2)
565
    self.rrole = m.group(3)
566
    self.ldisk = m.group(4)
567
    self.rdisk = m.group(5)
568

    
569
    self.is_standalone = self.cstatus == "StandAlone"
570
    self.is_wfconn = self.cstatus == "WFConnection"
571
    self.is_connected = self.cstatus == "Connected"
572
    self.is_primary = self.lrole == "Primary"
573
    self.is_secondary = self.lrole == "Secondary"
574
    self.peer_primary = self.rrole == "Primary"
575
    self.peer_secondary = self.rrole == "Secondary"
576
    self.both_primary = self.is_primary and self.peer_primary
577
    self.both_secondary = self.is_secondary and self.peer_secondary
578

    
579
    self.is_diskless = self.ldisk == "Diskless"
580
    self.is_disk_uptodate = self.ldisk == "UpToDate"
581

    
582
    m = self.SYNC_RE.match(procline)
583
    if m:
584
      self.sync_percent = float(m.group(1))
585
      hours = int(m.group(2))
586
      minutes = int(m.group(3))
587
      seconds = int(m.group(4))
588
      self.est_time = hours * 3600 + minutes * 60 + seconds
589
    else:
590
      self.sync_percent = None
591
      self.est_time = None
592

    
593
    self.is_sync_target = self.peer_sync_source = self.cstatus == "SyncTarget"
594
    self.peer_sync_target = self.is_sync_source = self.cstatus == "SyncSource"
595
    self.is_resync = self.is_sync_target or self.is_sync_source
596

    
597

    
598
class BaseDRBD(BlockDev):
599
  """Base DRBD class.
600

601
  This class contains a few bits of common functionality between the
602
  0.7 and 8.x versions of DRBD.
603

604
  """
605
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
606
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
607

    
608
  _DRBD_MAJOR = 147
609
  _ST_UNCONFIGURED = "Unconfigured"
610
  _ST_WFCONNECTION = "WFConnection"
611
  _ST_CONNECTED = "Connected"
612

    
613
  _STATUS_FILE = "/proc/drbd"
614

    
615
  @staticmethod
616
  def _GetProcData(filename=_STATUS_FILE):
617
    """Return data from /proc/drbd.
618

619
    """
620
    stat = open(filename, "r")
621
    try:
622
      data = stat.read().splitlines()
623
    finally:
624
      stat.close()
625
    if not data:
626
      raise errors.BlockDeviceError("Can't read any data from %s" % filename)
627
    return data
628

    
629
  @staticmethod
630
  def _MassageProcData(data):
631
    """Transform the output of _GetProdData into a nicer form.
632

633
    Returns:
634
      a dictionary of minor: joined lines from /proc/drbd for that minor
635

636
    """
637
    lmatch = re.compile("^ *([0-9]+):.*$")
638
    results = {}
639
    old_minor = old_line = None
640
    for line in data:
641
      lresult = lmatch.match(line)
642
      if lresult is not None:
643
        if old_minor is not None:
644
          results[old_minor] = old_line
645
        old_minor = int(lresult.group(1))
646
        old_line = line
647
      else:
648
        if old_minor is not None:
649
          old_line += " " + line.strip()
650
    # add last line
651
    if old_minor is not None:
652
      results[old_minor] = old_line
653
    return results
654

    
655
  @classmethod
656
  def _GetVersion(cls):
657
    """Return the DRBD version.
658

659
    This will return a dict with keys:
660
      k_major,
661
      k_minor,
662
      k_point,
663
      api,
664
      proto,
665
      proto2 (only on drbd > 8.2.X)
666

667
    """
668
    proc_data = cls._GetProcData()
669
    first_line = proc_data[0].strip()
670
    version = cls._VERSION_RE.match(first_line)
671
    if not version:
672
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
673
                                    first_line)
674

    
675
    values = version.groups()
676
    retval = {'k_major': int(values[0]),
677
              'k_minor': int(values[1]),
678
              'k_point': int(values[2]),
679
              'api': int(values[3]),
680
              'proto': int(values[4]),
681
             }
682
    if values[5] is not None:
683
      retval['proto2'] = values[5]
684

    
685
    return retval
686

    
687
  @staticmethod
688
  def _DevPath(minor):
689
    """Return the path to a drbd device for a given minor.
690

691
    """
692
    return "/dev/drbd%d" % minor
693

    
694
  @classmethod
695
  def _GetUsedDevs(cls):
696
    """Compute the list of used DRBD devices.
697

698
    """
699
    data = cls._GetProcData()
700

    
701
    used_devs = {}
702
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
703
    for line in data:
704
      match = valid_line.match(line)
705
      if not match:
706
        continue
707
      minor = int(match.group(1))
708
      state = match.group(2)
709
      if state == cls._ST_UNCONFIGURED:
710
        continue
711
      used_devs[minor] = state, line
712

    
713
    return used_devs
714

    
715
  def _SetFromMinor(self, minor):
716
    """Set our parameters based on the given minor.
717

718
    This sets our minor variable and our dev_path.
719

720
    """
721
    if minor is None:
722
      self.minor = self.dev_path = None
723
    else:
724
      self.minor = minor
725
      self.dev_path = self._DevPath(minor)
726

    
727
  @staticmethod
728
  def _CheckMetaSize(meta_device):
729
    """Check if the given meta device looks like a valid one.
730

731
    This currently only check the size, which must be around
732
    128MiB.
733

734
    """
735
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
736
    if result.failed:
737
      logger.Error("Failed to get device size: %s - %s" %
738
                   (result.fail_reason, result.output))
739
      return False
740
    try:
741
      sectors = int(result.stdout)
742
    except ValueError:
743
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
744
      return False
745
    bytes = sectors * 512
746
    if bytes < 128 * 1024 * 1024: # less than 128MiB
747
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
748
      return False
749
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
750
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
751
      return False
752
    return True
753

    
754
  def Rename(self, new_id):
755
    """Rename a device.
756

757
    This is not supported for drbd devices.
758

759
    """
760
    raise errors.ProgrammerError("Can't rename a drbd device")
761

    
762

    
763
class DRBD8(BaseDRBD):
764
  """DRBD v8.x block device.
765

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

770
  The unique_id for the drbd device is the (local_ip, local_port,
771
  remote_ip, remote_port) tuple, and it must have two children: the
772
  data device and the meta_device. The meta device is checked for
773
  valid size and is zeroed on create.
774

775
  """
776
  _MAX_MINORS = 255
777
  _PARSE_SHOW = None
778

    
779
  def __init__(self, unique_id, children):
780
    if children and children.count(None) > 0:
781
      children = []
782
    super(DRBD8, self).__init__(unique_id, children)
783
    self.major = self._DRBD_MAJOR
784
    version = self._GetVersion()
785
    if version['k_major'] != 8 :
786
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
787
                                    " requested ganeti usage: kernel is"
788
                                    " %s.%s, ganeti wants 8.x" %
789
                                    (version['k_major'], version['k_minor']))
790

    
791
    if len(children) not in (0, 2):
792
      raise ValueError("Invalid configuration data %s" % str(children))
793
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
794
      raise ValueError("Invalid configuration data %s" % str(unique_id))
795
    self._lhost, self._lport, self._rhost, self._rport = unique_id
796
    self.Attach()
797

    
798
  @classmethod
799
  def _InitMeta(cls, minor, dev_path):
800
    """Initialize a meta device.
801

802
    This will not work if the given minor is in use.
803

804
    """
805
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
806
                           "v08", dev_path, "0", "create-md"])
807
    if result.failed:
808
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
809
                                    result.output)
810

    
811
  @classmethod
812
  def _FindUnusedMinor(cls):
813
    """Find an unused DRBD device.
814

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

818
    """
819
    data = cls._GetProcData()
820

    
821
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
822
    used_line = re.compile("^ *([0-9]+): cs:")
823
    highest = None
824
    for line in data:
825
      match = unused_line.match(line)
826
      if match:
827
        return int(match.group(1))
828
      match = used_line.match(line)
829
      if match:
830
        minor = int(match.group(1))
831
        highest = max(highest, minor)
832
    if highest is None: # there are no minors in use at all
833
      return 0
834
    if highest >= cls._MAX_MINORS:
835
      logger.Error("Error: no free drbd minors!")
836
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
837
    return highest + 1
838

    
839
  @classmethod
840
  def _IsValidMeta(cls, meta_device):
841
    """Check if the given meta device looks like a valid one.
842

843
    """
844
    minor = cls._FindUnusedMinor()
845
    minor_path = cls._DevPath(minor)
846
    result = utils.RunCmd(["drbdmeta", minor_path,
847
                           "v08", meta_device, "0",
848
                           "dstate"])
849
    if result.failed:
850
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
851
      return False
852
    return True
853

    
854
  @classmethod
855
  def _GetShowParser(cls):
856
    """Return a parser for `drbd show` output.
857

858
    This will either create or return an already-create parser for the
859
    output of the command `drbd show`.
860

861
    """
862
    if cls._PARSE_SHOW is not None:
863
      return cls._PARSE_SHOW
864

    
865
    # pyparsing setup
866
    lbrace = pyp.Literal("{").suppress()
867
    rbrace = pyp.Literal("}").suppress()
868
    semi = pyp.Literal(";").suppress()
869
    # this also converts the value to an int
870
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
871

    
872
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
873
    defa = pyp.Literal("_is_default").suppress()
874
    dbl_quote = pyp.Literal('"').suppress()
875

    
876
    keyword = pyp.Word(pyp.alphanums + '-')
877

    
878
    # value types
879
    value = pyp.Word(pyp.alphanums + '_-/.:')
880
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
881
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
882
                 number)
883
    # meta device, extended syntax
884
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
885
                  number + pyp.Word(']').suppress())
886

    
887
    # a statement
888
    stmt = (~rbrace + keyword + ~lbrace +
889
            pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
890
            pyp.Optional(defa) + semi +
891
            pyp.Optional(pyp.restOfLine).suppress())
892

    
893
    # an entire section
894
    section_name = pyp.Word(pyp.alphas + '_')
895
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
896

    
897
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
898
    bnf.ignore(comment)
899

    
900
    cls._PARSE_SHOW = bnf
901

    
902
    return bnf
903

    
904
  @classmethod
905
  def _GetShowData(cls, minor):
906
    """Return the `drbdsetup show` data for a minor.
907

908
    """
909
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
910
    if result.failed:
911
      logger.Error("Can't display the drbd config: %s - %s" %
912
                   (result.fail_reason, result.output))
913
      return None
914
    return result.stdout
915

    
916
  @classmethod
917
  def _GetDevInfo(cls, out):
918
    """Parse details about a given DRBD minor.
919

920
    This return, if available, the local backing device (as a path)
921
    and the local and remote (ip, port) information from a string
922
    containing the output of the `drbdsetup show` command as returned
923
    by _GetShowData.
924

925
    """
926
    data = {}
927
    if not out:
928
      return data
929

    
930
    bnf = cls._GetShowParser()
931
    # run pyparse
932

    
933
    try:
934
      results = bnf.parseString(out)
935
    except pyp.ParseException, err:
936
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
937
                                    str(err))
938

    
939
    # and massage the results into our desired format
940
    for section in results:
941
      sname = section[0]
942
      if sname == "_this_host":
943
        for lst in section[1:]:
944
          if lst[0] == "disk":
945
            data["local_dev"] = lst[1]
946
          elif lst[0] == "meta-disk":
947
            data["meta_dev"] = lst[1]
948
            data["meta_index"] = lst[2]
949
          elif lst[0] == "address":
950
            data["local_addr"] = tuple(lst[1:])
951
      elif sname == "_remote_host":
952
        for lst in section[1:]:
953
          if lst[0] == "address":
954
            data["remote_addr"] = tuple(lst[1:])
955
    return data
956

    
957
  def _MatchesLocal(self, info):
958
    """Test if our local config matches with an existing device.
959

960
    The parameter should be as returned from `_GetDevInfo()`. This
961
    method tests if our local backing device is the same as the one in
962
    the info parameter, in effect testing if we look like the given
963
    device.
964

965
    """
966
    if self._children:
967
      backend, meta = self._children
968
    else:
969
      backend = meta = None
970

    
971
    if backend is not None:
972
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
973
    else:
974
      retval = ("local_dev" not in info)
975

    
976
    if meta is not None:
977
      retval = retval and ("meta_dev" in info and
978
                           info["meta_dev"] == meta.dev_path)
979
      retval = retval and ("meta_index" in info and
980
                           info["meta_index"] == 0)
981
    else:
982
      retval = retval and ("meta_dev" not in info and
983
                           "meta_index" not in info)
984
    return retval
985

    
986
  def _MatchesNet(self, info):
987
    """Test if our network config matches with an existing device.
988

989
    The parameter should be as returned from `_GetDevInfo()`. This
990
    method tests if our network configuration is the same as the one
991
    in the info parameter, in effect testing if we look like the given
992
    device.
993

994
    """
995
    if (((self._lhost is None and not ("local_addr" in info)) and
996
         (self._rhost is None and not ("remote_addr" in info)))):
997
      return True
998

    
999
    if self._lhost is None:
1000
      return False
1001

    
1002
    if not ("local_addr" in info and
1003
            "remote_addr" in info):
1004
      return False
1005

    
1006
    retval = (info["local_addr"] == (self._lhost, self._lport))
1007
    retval = (retval and
1008
              info["remote_addr"] == (self._rhost, self._rport))
1009
    return retval
1010

    
1011
  @classmethod
1012
  def _AssembleLocal(cls, minor, backend, meta):
1013
    """Configure the local part of a DRBD device.
1014

1015
    This is the first thing that must be done on an unconfigured DRBD
1016
    device. And it must be done only once.
1017

1018
    """
1019
    if not cls._IsValidMeta(meta):
1020
      return False
1021
    args = ["drbdsetup", cls._DevPath(minor), "disk",
1022
            backend, meta, "0", "-e", "detach", "--create-device"]
1023
    result = utils.RunCmd(args)
1024
    if result.failed:
1025
      logger.Error("Can't attach local disk: %s" % result.output)
1026
    return not result.failed
1027

    
1028
  @classmethod
1029
  def _AssembleNet(cls, minor, net_info, protocol,
1030
                   dual_pri=False, hmac=None, secret=None):
1031
    """Configure the network part of the device.
1032

1033
    """
1034
    lhost, lport, rhost, rport = net_info
1035
    if None in net_info:
1036
      # we don't want network connection and actually want to make
1037
      # sure its shutdown
1038
      return cls._ShutdownNet(minor)
1039

    
1040
    args = ["drbdsetup", cls._DevPath(minor), "net",
1041
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1042
            "-A", "discard-zero-changes",
1043
            "-B", "consensus",
1044
            "--create-device",
1045
            ]
1046
    if dual_pri:
1047
      args.append("-m")
1048
    if hmac and secret:
1049
      args.extend(["-a", hmac, "-x", secret])
1050
    result = utils.RunCmd(args)
1051
    if result.failed:
1052
      logger.Error("Can't setup network for dbrd device: %s - %s" %
1053
                   (result.fail_reason, result.output))
1054
      return False
1055

    
1056
    timeout = time.time() + 10
1057
    ok = False
1058
    while time.time() < timeout:
1059
      info = cls._GetDevInfo(cls._GetShowData(minor))
1060
      if not "local_addr" in info or not "remote_addr" in info:
1061
        time.sleep(1)
1062
        continue
1063
      if (info["local_addr"] != (lhost, lport) or
1064
          info["remote_addr"] != (rhost, rport)):
1065
        time.sleep(1)
1066
        continue
1067
      ok = True
1068
      break
1069
    if not ok:
1070
      logger.Error("Timeout while configuring network")
1071
      return False
1072
    return True
1073

    
1074
  def AddChildren(self, devices):
1075
    """Add a disk to the DRBD device.
1076

1077
    """
1078
    if self.minor is None:
1079
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1080
    if len(devices) != 2:
1081
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1082
    info = self._GetDevInfo(self._GetShowData(self.minor))
1083
    if "local_dev" in info:
1084
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1085
    backend, meta = devices
1086
    if backend.dev_path is None or meta.dev_path is None:
1087
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1088
    backend.Open()
1089
    meta.Open()
1090
    if not self._CheckMetaSize(meta.dev_path):
1091
      raise errors.BlockDeviceError("Invalid meta device size")
1092
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1093
    if not self._IsValidMeta(meta.dev_path):
1094
      raise errors.BlockDeviceError("Cannot initalize meta device")
1095

    
1096
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1097
      raise errors.BlockDeviceError("Can't attach to local storage")
1098
    self._children = devices
1099

    
1100
  def RemoveChildren(self, devices):
1101
    """Detach the drbd device from local storage.
1102

1103
    """
1104
    if self.minor is None:
1105
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1106
                                    " RemoveChildren")
1107
    # early return if we don't actually have backing storage
1108
    info = self._GetDevInfo(self._GetShowData(self.minor))
1109
    if "local_dev" not in info:
1110
      return
1111
    if len(self._children) != 2:
1112
      raise errors.BlockDeviceError("We don't have two children: %s" %
1113
                                    self._children)
1114
    if self._children.count(None) == 2: # we don't actually have children :)
1115
      logger.Error("Requested detach while detached")
1116
      return
1117
    if len(devices) != 2:
1118
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1119
    for child, dev in zip(self._children, devices):
1120
      if dev != child.dev_path:
1121
        raise errors.BlockDeviceError("Mismatch in local storage"
1122
                                      " (%s != %s) in RemoveChildren" %
1123
                                      (dev, child.dev_path))
1124

    
1125
    if not self._ShutdownLocal(self.minor):
1126
      raise errors.BlockDeviceError("Can't detach from local storage")
1127
    self._children = []
1128

    
1129
  def SetSyncSpeed(self, kbytes):
1130
    """Set the speed of the DRBD syncer.
1131

1132
    """
1133
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1134
    if self.minor is None:
1135
      logger.Info("Instance not attached to a device")
1136
      return False
1137
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1138
                           kbytes])
1139
    if result.failed:
1140
      logger.Error("Can't change syncer rate: %s - %s" %
1141
                   (result.fail_reason, result.output))
1142
    return not result.failed and children_result
1143

    
1144
  def GetProcStatus(self):
1145
    """Return device data from /proc.
1146

1147
    """
1148
    if self.minor is None:
1149
      raise errors.BlockDeviceError("GetStats() called while not attached")
1150
    proc_info = self._MassageProcData(self._GetProcData())
1151
    if self.minor not in proc_info:
1152
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1153
                                    self.minor)
1154
    return DRBD8Status(proc_info[self.minor])
1155

    
1156
  def GetSyncStatus(self):
1157
    """Returns the sync status of the device.
1158

1159
    Returns:
1160
     (sync_percent, estimated_time, is_degraded)
1161

1162
    If sync_percent is None, it means all is ok
1163
    If estimated_time is None, it means we can't esimate
1164
    the time needed, otherwise it's the time left in seconds.
1165

1166

1167
    We set the is_degraded parameter to True on two conditions:
1168
    network not connected or local disk missing.
1169

1170
    We compute the ldisk parameter based on wheter we have a local
1171
    disk or not.
1172

1173
    """
1174
    if self.minor is None and not self.Attach():
1175
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1176
    stats = self.GetProcStatus()
1177
    ldisk = not stats.is_disk_uptodate
1178
    is_degraded = not stats.is_connected
1179
    return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1180

    
1181
  def Open(self, force=False):
1182
    """Make the local state primary.
1183

1184
    If the 'force' parameter is given, the '-o' option is passed to
1185
    drbdsetup. Since this is a potentially dangerous operation, the
1186
    force flag should be only given after creation, when it actually
1187
    is mandatory.
1188

1189
    """
1190
    if self.minor is None and not self.Attach():
1191
      logger.Error("DRBD cannot attach to a device during open")
1192
      return False
1193
    cmd = ["drbdsetup", self.dev_path, "primary"]
1194
    if force:
1195
      cmd.append("-o")
1196
    result = utils.RunCmd(cmd)
1197
    if result.failed:
1198
      msg = ("Can't make drbd device primary: %s" % result.output)
1199
      logger.Error(msg)
1200
      raise errors.BlockDeviceError(msg)
1201

    
1202
  def Close(self):
1203
    """Make the local state secondary.
1204

1205
    This will, of course, fail if the device is in use.
1206

1207
    """
1208
    if self.minor is None and not self.Attach():
1209
      logger.Info("Instance not attached to a device")
1210
      raise errors.BlockDeviceError("Can't find device")
1211
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1212
    if result.failed:
1213
      msg = ("Can't switch drbd device to"
1214
             " secondary: %s" % result.output)
1215
      logger.Error(msg)
1216
      raise errors.BlockDeviceError(msg)
1217

    
1218
  def Attach(self):
1219
    """Find a DRBD device which matches our config and attach to it.
1220

1221
    In case of partially attached (local device matches but no network
1222
    setup), we perform the network attach. If successful, we re-test
1223
    the attach if can return success.
1224

1225
    """
1226
    for minor in self._GetUsedDevs():
1227
      info = self._GetDevInfo(self._GetShowData(minor))
1228
      match_l = self._MatchesLocal(info)
1229
      match_r = self._MatchesNet(info)
1230
      if match_l and match_r:
1231
        break
1232
      if match_l and not match_r and "local_addr" not in info:
1233
        res_r = self._AssembleNet(minor,
1234
                                  (self._lhost, self._lport,
1235
                                   self._rhost, self._rport),
1236
                                  "C")
1237
        if res_r:
1238
          if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1239
            break
1240
      # the weakest case: we find something that is only net attached
1241
      # even though we were passed some children at init time
1242
      if match_r and "local_dev" not in info:
1243
        break
1244

    
1245
      # this case must be considered only if we actually have local
1246
      # storage, i.e. not in diskless mode, because all diskless
1247
      # devices are equal from the point of view of local
1248
      # configuration
1249
      if (match_l and "local_dev" in info and
1250
          not match_r and "local_addr" in info):
1251
        # strange case - the device network part points to somewhere
1252
        # else, even though its local storage is ours; as we own the
1253
        # drbd space, we try to disconnect from the remote peer and
1254
        # reconnect to our correct one
1255
        if not self._ShutdownNet(minor):
1256
          raise errors.BlockDeviceError("Device has correct local storage,"
1257
                                        " wrong remote peer and is unable to"
1258
                                        " disconnect in order to attach to"
1259
                                        " the correct peer")
1260
        # note: _AssembleNet also handles the case when we don't want
1261
        # local storage (i.e. one or more of the _[lr](host|port) is
1262
        # None)
1263
        if (self._AssembleNet(minor, (self._lhost, self._lport,
1264
                                      self._rhost, self._rport), "C") and
1265
            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1266
          break
1267

    
1268
    else:
1269
      minor = None
1270

    
1271
    self._SetFromMinor(minor)
1272
    return minor is not None
1273

    
1274
  def Assemble(self):
1275
    """Assemble the drbd.
1276

1277
    Method:
1278
      - if we have a local backing device, we bind to it by:
1279
        - checking the list of used drbd devices
1280
        - check if the local minor use of any of them is our own device
1281
        - if yes, abort?
1282
        - if not, bind
1283
      - if we have a local/remote net info:
1284
        - redo the local backing device step for the remote device
1285
        - check if any drbd device is using the local port,
1286
          if yes abort
1287
        - check if any remote drbd device is using the remote
1288
          port, if yes abort (for now)
1289
        - bind our net port
1290
        - bind the remote net port
1291

1292
    """
1293
    self.Attach()
1294
    if self.minor is not None:
1295
      logger.Info("Already assembled")
1296
      return True
1297

    
1298
    result = super(DRBD8, self).Assemble()
1299
    if not result:
1300
      return result
1301

    
1302
    minor = self._FindUnusedMinor()
1303
    need_localdev_teardown = False
1304
    if self._children and self._children[0] and self._children[1]:
1305
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1306
                                   self._children[1].dev_path)
1307
      if not result:
1308
        return False
1309
      need_localdev_teardown = True
1310
    if self._lhost and self._lport and self._rhost and self._rport:
1311
      result = self._AssembleNet(minor,
1312
                                 (self._lhost, self._lport,
1313
                                  self._rhost, self._rport),
1314
                                 "C")
1315
      if not result:
1316
        if need_localdev_teardown:
1317
          # we will ignore failures from this
1318
          logger.Error("net setup failed, tearing down local device")
1319
          self._ShutdownAll(minor)
1320
        return False
1321
    self._SetFromMinor(minor)
1322
    return True
1323

    
1324
  @classmethod
1325
  def _ShutdownLocal(cls, minor):
1326
    """Detach from the local device.
1327

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

1331
    """
1332
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1333
    if result.failed:
1334
      logger.Error("Can't detach local device: %s" % result.output)
1335
    return not result.failed
1336

    
1337
  @classmethod
1338
  def _ShutdownNet(cls, minor):
1339
    """Disconnect from the remote peer.
1340

1341
    This fails if we don't have a local device.
1342

1343
    """
1344
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1345
    if result.failed:
1346
      logger.Error("Can't shutdown network: %s" % result.output)
1347
    return not result.failed
1348

    
1349
  @classmethod
1350
  def _ShutdownAll(cls, minor):
1351
    """Deactivate the device.
1352

1353
    This will, of course, fail if the device is in use.
1354

1355
    """
1356
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1357
    if result.failed:
1358
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1359
    return not result.failed
1360

    
1361
  def Shutdown(self):
1362
    """Shutdown the DRBD device.
1363

1364
    """
1365
    if self.minor is None and not self.Attach():
1366
      logger.Info("DRBD device not attached to a device during Shutdown")
1367
      return True
1368
    if not self._ShutdownAll(self.minor):
1369
      return False
1370
    self.minor = None
1371
    self.dev_path = None
1372
    return True
1373

    
1374
  def Remove(self):
1375
    """Stub remove for DRBD devices.
1376

1377
    """
1378
    return self.Shutdown()
1379

    
1380
  @classmethod
1381
  def Create(cls, unique_id, children, size):
1382
    """Create a new DRBD8 device.
1383

1384
    Since DRBD devices are not created per se, just assembled, this
1385
    function only initializes the metadata.
1386

1387
    """
1388
    if len(children) != 2:
1389
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1390
    meta = children[1]
1391
    meta.Assemble()
1392
    if not meta.Attach():
1393
      raise errors.BlockDeviceError("Can't attach to meta device")
1394
    if not cls._CheckMetaSize(meta.dev_path):
1395
      raise errors.BlockDeviceError("Invalid meta device size")
1396
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1397
    if not cls._IsValidMeta(meta.dev_path):
1398
      raise errors.BlockDeviceError("Cannot initalize meta device")
1399
    return cls(unique_id, children)
1400

    
1401
  def Grow(self, amount):
1402
    """Resize the DRBD device and its backing storage.
1403

1404
    """
1405
    if self.minor is None:
1406
      raise errors.ProgrammerError("drbd8: Grow called while not attached")
1407
    if len(self._children) != 2 or None in self._children:
1408
      raise errors.BlockDeviceError("Cannot grow diskless DRBD8 device")
1409
    self._children[0].Grow(amount)
1410
    result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1411
    if result.failed:
1412
      raise errors.BlockDeviceError("resize failed for %s: %s" %
1413
                                    (self.dev_path, result.output))
1414
    return
1415

    
1416

    
1417
class FileStorage(BlockDev):
1418
  """File device.
1419

1420
  This class represents the a file storage backend device.
1421

1422
  The unique_id for the file device is a (file_driver, file_path) tuple.
1423

1424
  """
1425
  def __init__(self, unique_id, children):
1426
    """Initalizes a file device backend.
1427

1428
    """
1429
    if children:
1430
      raise errors.BlockDeviceError("Invalid setup for file device")
1431
    super(FileStorage, self).__init__(unique_id, children)
1432
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1433
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1434
    self.driver = unique_id[0]
1435
    self.dev_path = unique_id[1]
1436

    
1437
  def Assemble(self):
1438
    """Assemble the device.
1439

1440
    Checks whether the file device exists, raises BlockDeviceError otherwise.
1441

1442
    """
1443
    if not os.path.exists(self.dev_path):
1444
      raise errors.BlockDeviceError("File device '%s' does not exist." %
1445
                                    self.dev_path)
1446
    return True
1447

    
1448
  def Shutdown(self):
1449
    """Shutdown the device.
1450

1451
    This is a no-op for the file type, as we don't deacivate
1452
    the file on shutdown.
1453

1454
    """
1455
    return True
1456

    
1457
  def Open(self, force=False):
1458
    """Make the device ready for I/O.
1459

1460
    This is a no-op for the file type.
1461

1462
    """
1463
    pass
1464

    
1465
  def Close(self):
1466
    """Notifies that the device will no longer be used for I/O.
1467

1468
    This is a no-op for the file type.
1469

1470
    """
1471
    pass
1472

    
1473
  def Remove(self):
1474
    """Remove the file backing the block device.
1475

1476
    Returns:
1477
      boolean indicating wheter removal of file was successful or not.
1478

1479
    """
1480
    if not os.path.exists(self.dev_path):
1481
      return True
1482
    try:
1483
      os.remove(self.dev_path)
1484
      return True
1485
    except OSError, err:
1486
      logger.Error("Can't remove file '%s': %s"
1487
                   % (self.dev_path, err))
1488
      return False
1489

    
1490
  def Attach(self):
1491
    """Attach to an existing file.
1492

1493
    Check if this file already exists.
1494

1495
    Returns:
1496
      boolean indicating if file exists or not.
1497

1498
    """
1499
    if os.path.exists(self.dev_path):
1500
      return True
1501
    return False
1502

    
1503
  @classmethod
1504
  def Create(cls, unique_id, children, size):
1505
    """Create a new file.
1506

1507
    Args:
1508
      children:
1509
      size: integer size of file in MiB
1510

1511
    Returns:
1512
      A ganeti.bdev.FileStorage object.
1513

1514
    """
1515
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1516
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1517
    dev_path = unique_id[1]
1518
    try:
1519
      f = open(dev_path, 'w')
1520
    except IOError, err:
1521
      raise errors.BlockDeviceError("Could not create '%'" % err)
1522
    else:
1523
      f.truncate(size * 1024 * 1024)
1524
      f.close()
1525

    
1526
    return FileStorage(unique_id, children)
1527

    
1528

    
1529
DEV_MAP = {
1530
  constants.LD_LV: LogicalVolume,
1531
  constants.LD_DRBD8: DRBD8,
1532
  constants.LD_FILE: FileStorage,
1533
  }
1534

    
1535

    
1536
def FindDevice(dev_type, unique_id, children):
1537
  """Search for an existing, assembled device.
1538

1539
  This will succeed only if the device exists and is assembled, but it
1540
  does not do any actions in order to activate the device.
1541

1542
  """
1543
  if dev_type not in DEV_MAP:
1544
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1545
  device = DEV_MAP[dev_type](unique_id, children)
1546
  if not device.Attach():
1547
    return None
1548
  return  device
1549

    
1550

    
1551
def AttachOrAssemble(dev_type, unique_id, children):
1552
  """Try to attach or assemble an existing device.
1553

1554
  This will attach to an existing assembled device or will assemble
1555
  the device, as needed, to bring it fully up.
1556

1557
  """
1558
  if dev_type not in DEV_MAP:
1559
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1560
  device = DEV_MAP[dev_type](unique_id, children)
1561
  if not device.Attach():
1562
    device.Assemble()
1563
    if not device.Attach():
1564
      raise errors.BlockDeviceError("Can't find a valid block device for"
1565
                                    " %s/%s/%s" %
1566
                                    (dev_type, unique_id, children))
1567
  return device
1568

    
1569

    
1570
def Create(dev_type, unique_id, children, size):
1571
  """Create a device.
1572

1573
  """
1574
  if dev_type not in DEV_MAP:
1575
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1576
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
1577
  return device