Statistics
| Branch: | Tag: | Revision:

root / lib / bdev.py @ abdf0113

History | View | Annotate | Download (46.5 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

    
259
  def __repr__(self):
260
    return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
261
            (self.__class__, self.unique_id, self._children,
262
             self.major, self.minor, self.dev_path))
263

    
264

    
265
class LogicalVolume(BlockDev):
266
  """Logical Volume block device.
267

268
  """
269
  def __init__(self, unique_id, children):
270
    """Attaches to a LV device.
271

272
    The unique_id is a tuple (vg_name, lv_name)
273

274
    """
275
    super(LogicalVolume, self).__init__(unique_id, children)
276
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
277
      raise ValueError("Invalid configuration data %s" % str(unique_id))
278
    self._vg_name, self._lv_name = unique_id
279
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
280
    self.Attach()
281

    
282
  @classmethod
283
  def Create(cls, unique_id, children, size):
284
    """Create a new logical volume.
285

286
    """
287
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
288
      raise ValueError("Invalid configuration data %s" % str(unique_id))
289
    vg_name, lv_name = unique_id
290
    pvs_info = cls.GetPVInfo(vg_name)
291
    if not pvs_info:
292
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
293
                                    vg_name)
294
    pvs_info.sort()
295
    pvs_info.reverse()
296

    
297
    pvlist = [ pv[1] for pv in pvs_info ]
298
    free_size = sum([ pv[0] for pv in pvs_info ])
299

    
300
    # The size constraint should have been checked from the master before
301
    # calling the create function.
302
    if free_size < size:
303
      raise errors.BlockDeviceError("Not enough free space: required %s,"
304
                                    " available %s" % (size, free_size))
305
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
306
                           vg_name] + pvlist)
307
    if result.failed:
308
      raise errors.BlockDeviceError("%s - %s" % (result.fail_reason,
309
                                                result.output))
310
    return LogicalVolume(unique_id, children)
311

    
312
  @staticmethod
313
  def GetPVInfo(vg_name):
314
    """Get the free space info for PVs in a volume group.
315

316
    Args:
317
      vg_name: the volume group name
318

319
    Returns:
320
      list of (free_space, name) with free_space in mebibytes
321

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

    
342
    return data
343

    
344
  def Remove(self):
345
    """Remove this logical volume.
346

347
    """
348
    if not self.minor and not self.Attach():
349
      # the LV does not exist
350
      return True
351
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
352
                           (self._vg_name, self._lv_name)])
353
    if result.failed:
354
      logger.Error("Can't lvremove: %s - %s" %
355
                   (result.fail_reason, result.output))
356

    
357
    return not result.failed
358

    
359
  def Rename(self, new_id):
360
    """Rename this logical volume.
361

362
    """
363
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
364
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
365
    new_vg, new_name = new_id
366
    if new_vg != self._vg_name:
367
      raise errors.ProgrammerError("Can't move a logical volume across"
368
                                   " volume groups (from %s to to %s)" %
369
                                   (self._vg_name, new_vg))
370
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
371
    if result.failed:
372
      raise errors.BlockDeviceError("Failed to rename the logical volume: %s" %
373
                                    result.output)
374
    self._lv_name = new_name
375
    self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
376

    
377
  def Attach(self):
378
    """Attach to an existing LV.
379

380
    This method will try to see if an existing and active LV exists
381
    which matches our name. If so, its major/minor will be
382
    recorded.
383

384
    """
385
    result = utils.RunCmd(["lvdisplay", self.dev_path])
386
    if result.failed:
387
      logger.Error("Can't find LV %s: %s, %s" %
388
                   (self.dev_path, result.fail_reason, result.output))
389
      return False
390
    match = re.compile("^ *Block device *([0-9]+):([0-9]+).*$")
391
    for line in result.stdout.splitlines():
392
      match_result = match.match(line)
393
      if match_result:
394
        self.major = int(match_result.group(1))
395
        self.minor = int(match_result.group(2))
396
        return True
397
    return False
398

    
399
  def Assemble(self):
400
    """Assemble the device.
401

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

406
    """
407
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
408
    if result.failed:
409
      logger.Error("Can't activate lv %s: %s" % (self.dev_path, result.output))
410
    return not result.failed
411

    
412
  def Shutdown(self):
413
    """Shutdown the device.
414

415
    This is a no-op for the LV device type, as we don't deactivate the
416
    volumes on shutdown.
417

418
    """
419
    return True
420

    
421
  def GetSyncStatus(self):
422
    """Returns the sync status of the device.
423

424
    If this device is a mirroring device, this function returns the
425
    status of the mirror.
426

427
    Returns:
428
     (sync_percent, estimated_time, is_degraded, ldisk)
429

430
    For logical volumes, sync_percent and estimated_time are always
431
    None (no recovery in progress, as we don't handle the mirrored LV
432
    case). The is_degraded parameter is the inverse of the ldisk
433
    parameter.
434

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

441
    """
442
    result = utils.RunCmd(["lvs", "--noheadings", "-olv_attr", self.dev_path])
443
    if result.failed:
444
      logger.Error("Can't display lv: %s - %s" %
445
                   (result.fail_reason, result.output))
446
      return None, None, True, True
447
    out = result.stdout.strip()
448
    # format: type/permissions/alloc/fixed_minor/state/open
449
    if len(out) != 6:
450
      logger.Debug("Error in lvs output: attrs=%s, len != 6" % out)
451
      return None, None, True, True
452
    ldisk = out[0] == 'v' # virtual volume, i.e. doesn't have
453
                          # backing storage
454
    return None, None, ldisk, ldisk
455

    
456
  def Open(self, force=False):
457
    """Make the device ready for I/O.
458

459
    This is a no-op for the LV device type.
460

461
    """
462
    pass
463

    
464
  def Close(self):
465
    """Notifies that the device will no longer be used for I/O.
466

467
    This is a no-op for the LV device type.
468

469
    """
470
    pass
471

    
472
  def Snapshot(self, size):
473
    """Create a snapshot copy of an lvm block device.
474

475
    """
476
    snap_name = self._lv_name + ".snap"
477

    
478
    # remove existing snapshot if found
479
    snap = LogicalVolume((self._vg_name, snap_name), None)
480
    snap.Remove()
481

    
482
    pvs_info = self.GetPVInfo(self._vg_name)
483
    if not pvs_info:
484
      raise errors.BlockDeviceError("Can't compute PV info for vg %s" %
485
                                    self._vg_name)
486
    pvs_info.sort()
487
    pvs_info.reverse()
488
    free_size, pv_name = pvs_info[0]
489
    if free_size < size:
490
      raise errors.BlockDeviceError("Not enough free space: required %s,"
491
                                    " available %s" % (size, free_size))
492

    
493
    result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
494
                           "-n%s" % snap_name, self.dev_path])
495
    if result.failed:
496
      raise errors.BlockDeviceError("command: %s error: %s - %s" %
497
                                    (result.cmd, result.fail_reason,
498
                                     result.output))
499

    
500
    return snap_name
501

    
502
  def SetInfo(self, text):
503
    """Update metadata with info text.
504

505
    """
506
    BlockDev.SetInfo(self, text)
507

    
508
    # Replace invalid characters
509
    text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
510
    text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
511

    
512
    # Only up to 128 characters are allowed
513
    text = text[:128]
514

    
515
    result = utils.RunCmd(["lvchange", "--addtag", text,
516
                           self.dev_path])
517
    if result.failed:
518
      raise errors.BlockDeviceError("Command: %s error: %s - %s" %
519
                                    (result.cmd, result.fail_reason,
520
                                     result.output))
521

    
522

    
523
class BaseDRBD(BlockDev):
524
  """Base DRBD class.
525

526
  This class contains a few bits of common functionality between the
527
  0.7 and 8.x versions of DRBD.
528

529
  """
530
  _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
531
                           r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
532

    
533
  _DRBD_MAJOR = 147
534
  _ST_UNCONFIGURED = "Unconfigured"
535
  _ST_WFCONNECTION = "WFConnection"
536
  _ST_CONNECTED = "Connected"
537

    
538
  @staticmethod
539
  def _GetProcData():
540
    """Return data from /proc/drbd.
541

542
    """
543
    stat = open("/proc/drbd", "r")
544
    try:
545
      data = stat.read().splitlines()
546
    finally:
547
      stat.close()
548
    if not data:
549
      raise errors.BlockDeviceError("Can't read any data from /proc/drbd")
550
    return data
551

    
552
  @staticmethod
553
  def _MassageProcData(data):
554
    """Transform the output of _GetProdData into a nicer form.
555

556
    Returns:
557
      a dictionary of minor: joined lines from /proc/drbd for that minor
558

559
    """
560
    lmatch = re.compile("^ *([0-9]+):.*$")
561
    results = {}
562
    old_minor = old_line = None
563
    for line in data:
564
      lresult = lmatch.match(line)
565
      if lresult is not None:
566
        if old_minor is not None:
567
          results[old_minor] = old_line
568
        old_minor = int(lresult.group(1))
569
        old_line = line
570
      else:
571
        if old_minor is not None:
572
          old_line += " " + line.strip()
573
    # add last line
574
    if old_minor is not None:
575
      results[old_minor] = old_line
576
    return results
577

    
578
  @classmethod
579
  def _GetVersion(cls):
580
    """Return the DRBD version.
581

582
    This will return a dict with keys:
583
      k_major,
584
      k_minor,
585
      k_point,
586
      api,
587
      proto,
588
      proto2 (only on drbd > 8.2.X)
589

590
    """
591
    proc_data = cls._GetProcData()
592
    first_line = proc_data[0].strip()
593
    version = cls._VERSION_RE.match(first_line)
594
    if not version:
595
      raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
596
                                    first_line)
597

    
598
    values = version.groups()
599
    retval = {'k_major': int(values[0]),
600
              'k_minor': int(values[1]),
601
              'k_point': int(values[2]),
602
              'api': int(values[3]),
603
              'proto': int(values[4]),
604
             }
605
    if values[5] is not None:
606
      retval['proto2'] = values[5]
607

    
608
    return retval
609

    
610
  @staticmethod
611
  def _DevPath(minor):
612
    """Return the path to a drbd device for a given minor.
613

614
    """
615
    return "/dev/drbd%d" % minor
616

    
617
  @classmethod
618
  def _GetUsedDevs(cls):
619
    """Compute the list of used DRBD devices.
620

621
    """
622
    data = cls._GetProcData()
623

    
624
    used_devs = {}
625
    valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
626
    for line in data:
627
      match = valid_line.match(line)
628
      if not match:
629
        continue
630
      minor = int(match.group(1))
631
      state = match.group(2)
632
      if state == cls._ST_UNCONFIGURED:
633
        continue
634
      used_devs[minor] = state, line
635

    
636
    return used_devs
637

    
638
  def _SetFromMinor(self, minor):
639
    """Set our parameters based on the given minor.
640

641
    This sets our minor variable and our dev_path.
642

643
    """
644
    if minor is None:
645
      self.minor = self.dev_path = None
646
    else:
647
      self.minor = minor
648
      self.dev_path = self._DevPath(minor)
649

    
650
  @staticmethod
651
  def _CheckMetaSize(meta_device):
652
    """Check if the given meta device looks like a valid one.
653

654
    This currently only check the size, which must be around
655
    128MiB.
656

657
    """
658
    result = utils.RunCmd(["blockdev", "--getsize", meta_device])
659
    if result.failed:
660
      logger.Error("Failed to get device size: %s - %s" %
661
                   (result.fail_reason, result.output))
662
      return False
663
    try:
664
      sectors = int(result.stdout)
665
    except ValueError:
666
      logger.Error("Invalid output from blockdev: '%s'" % result.stdout)
667
      return False
668
    bytes = sectors * 512
669
    if bytes < 128 * 1024 * 1024: # less than 128MiB
670
      logger.Error("Meta device too small (%.2fMib)" % (bytes / 1024 / 1024))
671
      return False
672
    if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
673
      logger.Error("Meta device too big (%.2fMiB)" % (bytes / 1024 / 1024))
674
      return False
675
    return True
676

    
677
  def Rename(self, new_id):
678
    """Rename a device.
679

680
    This is not supported for drbd devices.
681

682
    """
683
    raise errors.ProgrammerError("Can't rename a drbd device")
684

    
685

    
686
class DRBD8(BaseDRBD):
687
  """DRBD v8.x block device.
688

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

693
  The unique_id for the drbd device is the (local_ip, local_port,
694
  remote_ip, remote_port) tuple, and it must have two children: the
695
  data device and the meta_device. The meta device is checked for
696
  valid size and is zeroed on create.
697

698
  """
699
  _MAX_MINORS = 255
700
  _PARSE_SHOW = None
701

    
702
  def __init__(self, unique_id, children):
703
    if children and children.count(None) > 0:
704
      children = []
705
    super(DRBD8, self).__init__(unique_id, children)
706
    self.major = self._DRBD_MAJOR
707
    version = self._GetVersion()
708
    if version['k_major'] != 8 :
709
      raise errors.BlockDeviceError("Mismatch in DRBD kernel version and"
710
                                    " requested ganeti usage: kernel is"
711
                                    " %s.%s, ganeti wants 8.x" %
712
                                    (version['k_major'], version['k_minor']))
713

    
714
    if len(children) not in (0, 2):
715
      raise ValueError("Invalid configuration data %s" % str(children))
716
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 4:
717
      raise ValueError("Invalid configuration data %s" % str(unique_id))
718
    self._lhost, self._lport, self._rhost, self._rport = unique_id
719
    self.Attach()
720

    
721
  @classmethod
722
  def _InitMeta(cls, minor, dev_path):
723
    """Initialize a meta device.
724

725
    This will not work if the given minor is in use.
726

727
    """
728
    result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
729
                           "v08", dev_path, "0", "create-md"])
730
    if result.failed:
731
      raise errors.BlockDeviceError("Can't initialize meta device: %s" %
732
                                    result.output)
733

    
734
  @classmethod
735
  def _FindUnusedMinor(cls):
736
    """Find an unused DRBD device.
737

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

741
    """
742
    data = cls._GetProcData()
743

    
744
    unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
745
    used_line = re.compile("^ *([0-9]+): cs:")
746
    highest = None
747
    for line in data:
748
      match = unused_line.match(line)
749
      if match:
750
        return int(match.group(1))
751
      match = used_line.match(line)
752
      if match:
753
        minor = int(match.group(1))
754
        highest = max(highest, minor)
755
    if highest is None: # there are no minors in use at all
756
      return 0
757
    if highest >= cls._MAX_MINORS:
758
      logger.Error("Error: no free drbd minors!")
759
      raise errors.BlockDeviceError("Can't find a free DRBD minor")
760
    return highest + 1
761

    
762
  @classmethod
763
  def _IsValidMeta(cls, meta_device):
764
    """Check if the given meta device looks like a valid one.
765

766
    """
767
    minor = cls._FindUnusedMinor()
768
    minor_path = cls._DevPath(minor)
769
    result = utils.RunCmd(["drbdmeta", minor_path,
770
                           "v08", meta_device, "0",
771
                           "dstate"])
772
    if result.failed:
773
      logger.Error("Invalid meta device %s: %s" % (meta_device, result.output))
774
      return False
775
    return True
776

    
777
  @classmethod
778
  def _GetShowParser(cls):
779
    """Return a parser for `drbd show` output.
780

781
    This will either create or return an already-create parser for the
782
    output of the command `drbd show`.
783

784
    """
785
    if cls._PARSE_SHOW is not None:
786
      return cls._PARSE_SHOW
787

    
788
    # pyparsing setup
789
    lbrace = pyp.Literal("{").suppress()
790
    rbrace = pyp.Literal("}").suppress()
791
    semi = pyp.Literal(";").suppress()
792
    # this also converts the value to an int
793
    number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
794

    
795
    comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
796
    defa = pyp.Literal("_is_default").suppress()
797
    dbl_quote = pyp.Literal('"').suppress()
798

    
799
    keyword = pyp.Word(pyp.alphanums + '-')
800

    
801
    # value types
802
    value = pyp.Word(pyp.alphanums + '_-/.:')
803
    quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
804
    addr_port = (pyp.Word(pyp.nums + '.') + pyp.Literal(':').suppress() +
805
                 number)
806
    # meta device, extended syntax
807
    meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
808
                  number + pyp.Word(']').suppress())
809

    
810
    # a statement
811
    stmt = (~rbrace + keyword + ~lbrace +
812
            pyp.Optional(addr_port ^ value ^ quoted ^ meta_value) +
813
            pyp.Optional(defa) + semi +
814
            pyp.Optional(pyp.restOfLine).suppress())
815

    
816
    # an entire section
817
    section_name = pyp.Word(pyp.alphas + '_')
818
    section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
819

    
820
    bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
821
    bnf.ignore(comment)
822

    
823
    cls._PARSE_SHOW = bnf
824

    
825
    return bnf
826

    
827
  @classmethod
828
  def _GetShowData(cls, minor):
829
    """Return the `drbdsetup show` data for a minor.
830

831
    """
832
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
833
    if result.failed:
834
      logger.Error("Can't display the drbd config: %s - %s" %
835
                   (result.fail_reason, result.output))
836
      return None
837
    return result.stdout
838

    
839
  @classmethod
840
  def _GetDevInfo(cls, out):
841
    """Parse details about a given DRBD minor.
842

843
    This return, if available, the local backing device (as a path)
844
    and the local and remote (ip, port) information from a string
845
    containing the output of the `drbdsetup show` command as returned
846
    by _GetShowData.
847

848
    """
849
    data = {}
850
    if not out:
851
      return data
852

    
853
    bnf = cls._GetShowParser()
854
    # run pyparse
855

    
856
    try:
857
      results = bnf.parseString(out)
858
    except pyp.ParseException, err:
859
      raise errors.BlockDeviceError("Can't parse drbdsetup show output: %s" %
860
                                    str(err))
861

    
862
    # and massage the results into our desired format
863
    for section in results:
864
      sname = section[0]
865
      if sname == "_this_host":
866
        for lst in section[1:]:
867
          if lst[0] == "disk":
868
            data["local_dev"] = lst[1]
869
          elif lst[0] == "meta-disk":
870
            data["meta_dev"] = lst[1]
871
            data["meta_index"] = lst[2]
872
          elif lst[0] == "address":
873
            data["local_addr"] = tuple(lst[1:])
874
      elif sname == "_remote_host":
875
        for lst in section[1:]:
876
          if lst[0] == "address":
877
            data["remote_addr"] = tuple(lst[1:])
878
    return data
879

    
880
  def _MatchesLocal(self, info):
881
    """Test if our local config matches with an existing device.
882

883
    The parameter should be as returned from `_GetDevInfo()`. This
884
    method tests if our local backing device is the same as the one in
885
    the info parameter, in effect testing if we look like the given
886
    device.
887

888
    """
889
    if self._children:
890
      backend, meta = self._children
891
    else:
892
      backend = meta = None
893

    
894
    if backend is not None:
895
      retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
896
    else:
897
      retval = ("local_dev" not in info)
898

    
899
    if meta is not None:
900
      retval = retval and ("meta_dev" in info and
901
                           info["meta_dev"] == meta.dev_path)
902
      retval = retval and ("meta_index" in info and
903
                           info["meta_index"] == 0)
904
    else:
905
      retval = retval and ("meta_dev" not in info and
906
                           "meta_index" not in info)
907
    return retval
908

    
909
  def _MatchesNet(self, info):
910
    """Test if our network config matches with an existing device.
911

912
    The parameter should be as returned from `_GetDevInfo()`. This
913
    method tests if our network configuration is the same as the one
914
    in the info parameter, in effect testing if we look like the given
915
    device.
916

917
    """
918
    if (((self._lhost is None and not ("local_addr" in info)) and
919
         (self._rhost is None and not ("remote_addr" in info)))):
920
      return True
921

    
922
    if self._lhost is None:
923
      return False
924

    
925
    if not ("local_addr" in info and
926
            "remote_addr" in info):
927
      return False
928

    
929
    retval = (info["local_addr"] == (self._lhost, self._lport))
930
    retval = (retval and
931
              info["remote_addr"] == (self._rhost, self._rport))
932
    return retval
933

    
934
  @classmethod
935
  def _AssembleLocal(cls, minor, backend, meta):
936
    """Configure the local part of a DRBD device.
937

938
    This is the first thing that must be done on an unconfigured DRBD
939
    device. And it must be done only once.
940

941
    """
942
    if not cls._IsValidMeta(meta):
943
      return False
944
    args = ["drbdsetup", cls._DevPath(minor), "disk",
945
            backend, meta, "0", "-e", "detach", "--create-device"]
946
    result = utils.RunCmd(args)
947
    if result.failed:
948
      logger.Error("Can't attach local disk: %s" % result.output)
949
    return not result.failed
950

    
951
  @classmethod
952
  def _AssembleNet(cls, minor, net_info, protocol,
953
                   dual_pri=False, hmac=None, secret=None):
954
    """Configure the network part of the device.
955

956
    """
957
    lhost, lport, rhost, rport = net_info
958
    if None in net_info:
959
      # we don't want network connection and actually want to make
960
      # sure its shutdown
961
      return cls._ShutdownNet(minor)
962

    
963
    args = ["drbdsetup", cls._DevPath(minor), "net",
964
            "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
965
            "-A", "discard-zero-changes",
966
            "-B", "consensus",
967
            "--create-device",
968
            ]
969
    if dual_pri:
970
      args.append("-m")
971
    if hmac and secret:
972
      args.extend(["-a", hmac, "-x", secret])
973
    result = utils.RunCmd(args)
974
    if result.failed:
975
      logger.Error("Can't setup network for dbrd device: %s - %s" %
976
                   (result.fail_reason, result.output))
977
      return False
978

    
979
    timeout = time.time() + 10
980
    ok = False
981
    while time.time() < timeout:
982
      info = cls._GetDevInfo(cls._GetShowData(minor))
983
      if not "local_addr" in info or not "remote_addr" in info:
984
        time.sleep(1)
985
        continue
986
      if (info["local_addr"] != (lhost, lport) or
987
          info["remote_addr"] != (rhost, rport)):
988
        time.sleep(1)
989
        continue
990
      ok = True
991
      break
992
    if not ok:
993
      logger.Error("Timeout while configuring network")
994
      return False
995
    return True
996

    
997
  def AddChildren(self, devices):
998
    """Add a disk to the DRBD device.
999

1000
    """
1001
    if self.minor is None:
1002
      raise errors.BlockDeviceError("Can't attach to dbrd8 during AddChildren")
1003
    if len(devices) != 2:
1004
      raise errors.BlockDeviceError("Need two devices for AddChildren")
1005
    info = self._GetDevInfo(self._GetShowData(self.minor))
1006
    if "local_dev" in info:
1007
      raise errors.BlockDeviceError("DRBD8 already attached to a local disk")
1008
    backend, meta = devices
1009
    if backend.dev_path is None or meta.dev_path is None:
1010
      raise errors.BlockDeviceError("Children not ready during AddChildren")
1011
    backend.Open()
1012
    meta.Open()
1013
    if not self._CheckMetaSize(meta.dev_path):
1014
      raise errors.BlockDeviceError("Invalid meta device size")
1015
    self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1016
    if not self._IsValidMeta(meta.dev_path):
1017
      raise errors.BlockDeviceError("Cannot initalize meta device")
1018

    
1019
    if not self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path):
1020
      raise errors.BlockDeviceError("Can't attach to local storage")
1021
    self._children = devices
1022

    
1023
  def RemoveChildren(self, devices):
1024
    """Detach the drbd device from local storage.
1025

1026
    """
1027
    if self.minor is None:
1028
      raise errors.BlockDeviceError("Can't attach to drbd8 during"
1029
                                    " RemoveChildren")
1030
    # early return if we don't actually have backing storage
1031
    info = self._GetDevInfo(self._GetShowData(self.minor))
1032
    if "local_dev" not in info:
1033
      return
1034
    if len(self._children) != 2:
1035
      raise errors.BlockDeviceError("We don't have two children: %s" %
1036
                                    self._children)
1037
    if self._children.count(None) == 2: # we don't actually have children :)
1038
      logger.Error("Requested detach while detached")
1039
      return
1040
    if len(devices) != 2:
1041
      raise errors.BlockDeviceError("We need two children in RemoveChildren")
1042
    for child, dev in zip(self._children, devices):
1043
      if dev != child.dev_path:
1044
        raise errors.BlockDeviceError("Mismatch in local storage"
1045
                                      " (%s != %s) in RemoveChildren" %
1046
                                      (dev, child.dev_path))
1047

    
1048
    if not self._ShutdownLocal(self.minor):
1049
      raise errors.BlockDeviceError("Can't detach from local storage")
1050
    self._children = []
1051

    
1052
  def SetSyncSpeed(self, kbytes):
1053
    """Set the speed of the DRBD syncer.
1054

1055
    """
1056
    children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1057
    if self.minor is None:
1058
      logger.Info("Instance not attached to a device")
1059
      return False
1060
    result = utils.RunCmd(["drbdsetup", self.dev_path, "syncer", "-r", "%d" %
1061
                           kbytes])
1062
    if result.failed:
1063
      logger.Error("Can't change syncer rate: %s - %s" %
1064
                   (result.fail_reason, result.output))
1065
    return not result.failed and children_result
1066

    
1067
  def GetSyncStatus(self):
1068
    """Returns the sync status of the device.
1069

1070
    Returns:
1071
     (sync_percent, estimated_time, is_degraded)
1072

1073
    If sync_percent is None, it means all is ok
1074
    If estimated_time is None, it means we can't esimate
1075
    the time needed, otherwise it's the time left in seconds.
1076

1077

1078
    We set the is_degraded parameter to True on two conditions:
1079
    network not connected or local disk missing.
1080

1081
    We compute the ldisk parameter based on wheter we have a local
1082
    disk or not.
1083

1084
    """
1085
    if self.minor is None and not self.Attach():
1086
      raise errors.BlockDeviceError("Can't attach to device in GetSyncStatus")
1087
    proc_info = self._MassageProcData(self._GetProcData())
1088
    if self.minor not in proc_info:
1089
      raise errors.BlockDeviceError("Can't find myself in /proc (minor %d)" %
1090
                                    self.minor)
1091
    line = proc_info[self.minor]
1092
    match = re.match("^.*sync'ed: *([0-9.]+)%.*"
1093
                     " finish: ([0-9]+):([0-9]+):([0-9]+) .*$", line)
1094
    if match:
1095
      sync_percent = float(match.group(1))
1096
      hours = int(match.group(2))
1097
      minutes = int(match.group(3))
1098
      seconds = int(match.group(4))
1099
      est_time = hours * 3600 + minutes * 60 + seconds
1100
    else:
1101
      sync_percent = None
1102
      est_time = None
1103
    match = re.match("^ *\d+: cs:(\w+).*ds:(\w+)/(\w+).*$", line)
1104
    if not match:
1105
      raise errors.BlockDeviceError("Can't find my data in /proc (minor %d)" %
1106
                                    self.minor)
1107
    client_state = match.group(1)
1108
    local_disk_state = match.group(2)
1109
    ldisk = local_disk_state != "UpToDate"
1110
    is_degraded = client_state != "Connected"
1111
    return sync_percent, est_time, is_degraded or ldisk, ldisk
1112

    
1113
  def Open(self, force=False):
1114
    """Make the local state primary.
1115

1116
    If the 'force' parameter is given, the '-o' option is passed to
1117
    drbdsetup. Since this is a potentially dangerous operation, the
1118
    force flag should be only given after creation, when it actually
1119
    is mandatory.
1120

1121
    """
1122
    if self.minor is None and not self.Attach():
1123
      logger.Error("DRBD cannot attach to a device during open")
1124
      return False
1125
    cmd = ["drbdsetup", self.dev_path, "primary"]
1126
    if force:
1127
      cmd.append("-o")
1128
    result = utils.RunCmd(cmd)
1129
    if result.failed:
1130
      msg = ("Can't make drbd device primary: %s" % result.output)
1131
      logger.Error(msg)
1132
      raise errors.BlockDeviceError(msg)
1133

    
1134
  def Close(self):
1135
    """Make the local state secondary.
1136

1137
    This will, of course, fail if the device is in use.
1138

1139
    """
1140
    if self.minor is None and not self.Attach():
1141
      logger.Info("Instance not attached to a device")
1142
      raise errors.BlockDeviceError("Can't find device")
1143
    result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1144
    if result.failed:
1145
      msg = ("Can't switch drbd device to"
1146
             " secondary: %s" % result.output)
1147
      logger.Error(msg)
1148
      raise errors.BlockDeviceError(msg)
1149

    
1150
  def Attach(self):
1151
    """Find a DRBD device which matches our config and attach to it.
1152

1153
    In case of partially attached (local device matches but no network
1154
    setup), we perform the network attach. If successful, we re-test
1155
    the attach if can return success.
1156

1157
    """
1158
    for minor in self._GetUsedDevs():
1159
      info = self._GetDevInfo(self._GetShowData(minor))
1160
      match_l = self._MatchesLocal(info)
1161
      match_r = self._MatchesNet(info)
1162
      if match_l and match_r:
1163
        break
1164
      if match_l and not match_r and "local_addr" not in info:
1165
        res_r = self._AssembleNet(minor,
1166
                                  (self._lhost, self._lport,
1167
                                   self._rhost, self._rport),
1168
                                  "C")
1169
        if res_r:
1170
          if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1171
            break
1172
      # the weakest case: we find something that is only net attached
1173
      # even though we were passed some children at init time
1174
      if match_r and "local_dev" not in info:
1175
        break
1176

    
1177
      # this case must be considered only if we actually have local
1178
      # storage, i.e. not in diskless mode, because all diskless
1179
      # devices are equal from the point of view of local
1180
      # configuration
1181
      if (match_l and "local_dev" in info and
1182
          not match_r and "local_addr" in info):
1183
        # strange case - the device network part points to somewhere
1184
        # else, even though its local storage is ours; as we own the
1185
        # drbd space, we try to disconnect from the remote peer and
1186
        # reconnect to our correct one
1187
        if not self._ShutdownNet(minor):
1188
          raise errors.BlockDeviceError("Device has correct local storage,"
1189
                                        " wrong remote peer and is unable to"
1190
                                        " disconnect in order to attach to"
1191
                                        " the correct peer")
1192
        # note: _AssembleNet also handles the case when we don't want
1193
        # local storage (i.e. one or more of the _[lr](host|port) is
1194
        # None)
1195
        if (self._AssembleNet(minor, (self._lhost, self._lport,
1196
                                      self._rhost, self._rport), "C") and
1197
            self._MatchesNet(self._GetDevInfo(self._GetShowData(minor)))):
1198
          break
1199

    
1200
    else:
1201
      minor = None
1202

    
1203
    self._SetFromMinor(minor)
1204
    return minor is not None
1205

    
1206
  def Assemble(self):
1207
    """Assemble the drbd.
1208

1209
    Method:
1210
      - if we have a local backing device, we bind to it by:
1211
        - checking the list of used drbd devices
1212
        - check if the local minor use of any of them is our own device
1213
        - if yes, abort?
1214
        - if not, bind
1215
      - if we have a local/remote net info:
1216
        - redo the local backing device step for the remote device
1217
        - check if any drbd device is using the local port,
1218
          if yes abort
1219
        - check if any remote drbd device is using the remote
1220
          port, if yes abort (for now)
1221
        - bind our net port
1222
        - bind the remote net port
1223

1224
    """
1225
    self.Attach()
1226
    if self.minor is not None:
1227
      logger.Info("Already assembled")
1228
      return True
1229

    
1230
    result = super(DRBD8, self).Assemble()
1231
    if not result:
1232
      return result
1233

    
1234
    minor = self._FindUnusedMinor()
1235
    need_localdev_teardown = False
1236
    if self._children and self._children[0] and self._children[1]:
1237
      result = self._AssembleLocal(minor, self._children[0].dev_path,
1238
                                   self._children[1].dev_path)
1239
      if not result:
1240
        return False
1241
      need_localdev_teardown = True
1242
    if self._lhost and self._lport and self._rhost and self._rport:
1243
      result = self._AssembleNet(minor,
1244
                                 (self._lhost, self._lport,
1245
                                  self._rhost, self._rport),
1246
                                 "C")
1247
      if not result:
1248
        if need_localdev_teardown:
1249
          # we will ignore failures from this
1250
          logger.Error("net setup failed, tearing down local device")
1251
          self._ShutdownAll(minor)
1252
        return False
1253
    self._SetFromMinor(minor)
1254
    return True
1255

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

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

1263
    """
1264
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1265
    if result.failed:
1266
      logger.Error("Can't detach local device: %s" % result.output)
1267
    return not result.failed
1268

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

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

1275
    """
1276
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1277
    if result.failed:
1278
      logger.Error("Can't shutdown network: %s" % result.output)
1279
    return not result.failed
1280

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

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

1287
    """
1288
    result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1289
    if result.failed:
1290
      logger.Error("Can't shutdown drbd device: %s" % result.output)
1291
    return not result.failed
1292

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

1296
    """
1297
    if self.minor is None and not self.Attach():
1298
      logger.Info("DRBD device not attached to a device during Shutdown")
1299
      return True
1300
    if not self._ShutdownAll(self.minor):
1301
      return False
1302
    self.minor = None
1303
    self.dev_path = None
1304
    return True
1305

    
1306
  def Remove(self):
1307
    """Stub remove for DRBD devices.
1308

1309
    """
1310
    return self.Shutdown()
1311

    
1312
  @classmethod
1313
  def Create(cls, unique_id, children, size):
1314
    """Create a new DRBD8 device.
1315

1316
    Since DRBD devices are not created per se, just assembled, this
1317
    function only initializes the metadata.
1318

1319
    """
1320
    if len(children) != 2:
1321
      raise errors.ProgrammerError("Invalid setup for the drbd device")
1322
    meta = children[1]
1323
    meta.Assemble()
1324
    if not meta.Attach():
1325
      raise errors.BlockDeviceError("Can't attach to meta device")
1326
    if not cls._CheckMetaSize(meta.dev_path):
1327
      raise errors.BlockDeviceError("Invalid meta device size")
1328
    cls._InitMeta(cls._FindUnusedMinor(), meta.dev_path)
1329
    if not cls._IsValidMeta(meta.dev_path):
1330
      raise errors.BlockDeviceError("Cannot initalize meta device")
1331
    return cls(unique_id, children)
1332

    
1333

    
1334
class FileStorage(BlockDev):
1335
  """File device.
1336

1337
  This class represents the a file storage backend device.
1338

1339
  The unique_id for the file device is a (file_driver, file_path) tuple.
1340

1341
  """
1342
  def __init__(self, unique_id, children):
1343
    """Initalizes a file device backend.
1344

1345
    """
1346
    if children:
1347
      raise errors.BlockDeviceError("Invalid setup for file device")
1348
    super(FileStorage, self).__init__(unique_id, children)
1349
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1350
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1351
    self.driver = unique_id[0]
1352
    self.dev_path = unique_id[1]
1353

    
1354
  def Assemble(self):
1355
    """Assemble the device.
1356

1357
    Checks whether the file device exists, raises BlockDeviceError otherwise.
1358

1359
    """
1360
    if not os.path.exists(self.dev_path):
1361
      raise errors.BlockDeviceError("File device '%s' does not exist." %
1362
                                    self.dev_path)
1363
    return True
1364

    
1365
  def Shutdown(self):
1366
    """Shutdown the device.
1367

1368
    This is a no-op for the file type, as we don't deacivate
1369
    the file on shutdown.
1370

1371
    """
1372
    return True
1373

    
1374
  def Open(self, force=False):
1375
    """Make the device ready for I/O.
1376

1377
    This is a no-op for the file type.
1378

1379
    """
1380
    pass
1381

    
1382
  def Close(self):
1383
    """Notifies that the device will no longer be used for I/O.
1384

1385
    This is a no-op for the file type.
1386

1387
    """
1388
    pass
1389

    
1390
  def Remove(self):
1391
    """Remove the file backing the block device.
1392

1393
    Returns:
1394
      boolean indicating wheter removal of file was successful or not.
1395

1396
    """
1397
    if not os.path.exists(self.dev_path):
1398
      return True
1399
    try:
1400
      os.remove(self.dev_path)
1401
      return True
1402
    except OSError, err:
1403
      logger.Error("Can't remove file '%s': %s"
1404
                   % (self.dev_path, err))
1405
      return False
1406

    
1407
  def Attach(self):
1408
    """Attach to an existing file.
1409

1410
    Check if this file already exists.
1411

1412
    Returns:
1413
      boolean indicating if file exists or not.
1414

1415
    """
1416
    if os.path.exists(self.dev_path):
1417
      return True
1418
    return False
1419

    
1420
  @classmethod
1421
  def Create(cls, unique_id, children, size):
1422
    """Create a new file.
1423

1424
    Args:
1425
      children:
1426
      size: integer size of file in MiB
1427

1428
    Returns:
1429
      A ganeti.bdev.FileStorage object.
1430

1431
    """
1432
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1433
      raise ValueError("Invalid configuration data %s" % str(unique_id))
1434
    dev_path = unique_id[1]
1435
    try:
1436
      f = open(dev_path, 'w')
1437
    except IOError, err:
1438
      raise errors.BlockDeviceError("Could not create '%'" % err)
1439
    else:
1440
      f.truncate(size * 1024 * 1024)
1441
      f.close()
1442

    
1443
    return FileStorage(unique_id, children)
1444

    
1445

    
1446
DEV_MAP = {
1447
  constants.LD_LV: LogicalVolume,
1448
  constants.LD_DRBD8: DRBD8,
1449
  constants.LD_FILE: FileStorage,
1450
  }
1451

    
1452

    
1453
def FindDevice(dev_type, unique_id, children):
1454
  """Search for an existing, assembled device.
1455

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

1459
  """
1460
  if dev_type not in DEV_MAP:
1461
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1462
  device = DEV_MAP[dev_type](unique_id, children)
1463
  if not device.Attach():
1464
    return None
1465
  return  device
1466

    
1467

    
1468
def AttachOrAssemble(dev_type, unique_id, children):
1469
  """Try to attach or assemble an existing device.
1470

1471
  This will attach to an existing assembled device or will assemble
1472
  the device, as needed, to bring it fully up.
1473

1474
  """
1475
  if dev_type not in DEV_MAP:
1476
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1477
  device = DEV_MAP[dev_type](unique_id, children)
1478
  if not device.Attach():
1479
    device.Assemble()
1480
    if not device.Attach():
1481
      raise errors.BlockDeviceError("Can't find a valid block device for"
1482
                                    " %s/%s/%s" %
1483
                                    (dev_type, unique_id, children))
1484
  return device
1485

    
1486

    
1487
def Create(dev_type, unique_id, children, size):
1488
  """Create a device.
1489

1490
  """
1491
  if dev_type not in DEV_MAP:
1492
    raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1493
  device = DEV_MAP[dev_type].Create(unique_id, children, size)
1494
  return device