4 # Copyright (C) 2006, 2007 Google Inc.
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.
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.
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
22 """Block device abstraction"""
27 import pyparsing as pyp
31 from ganeti import utils
32 from ganeti import errors
33 from ganeti import constants
36 def _IgnoreError(fn, *args, **kwargs):
37 """Executes the given function, ignoring BlockDeviceErrors.
39 This is used in order to simplify the execution of cleanup or
43 @return: True when fn didn't raise an exception, False otherwise
49 except errors.BlockDeviceError, err:
50 logging.warning("Caught BlockDeviceError but ignoring: %s" % str(err))
54 def _ThrowError(msg, *args):
55 """Log an error to the node daemon and the raise an exception.
58 @param msg: the text of the exception
59 @raise errors.BlockDeviceError
65 raise errors.BlockDeviceError(msg)
68 class BlockDev(object):
69 """Block device abstract class.
71 A block device can be in the following states:
72 - not existing on the system, and by `Create()` it goes into:
73 - existing but not setup/not active, and by `Assemble()` goes into:
74 - active read-write and by `Open()` it goes into
75 - online (=used, or ready for use)
77 A device can also be online but read-only, however we are not using
78 the readonly state (LV has it, if needed in the future) and we are
79 usually looking at this like at a stack, so it's easier to
80 conceptualise the transition from not-existing to online and back
83 The many different states of the device are due to the fact that we
84 need to cover many device types:
85 - logical volumes are created, lvchange -a y $lv, and used
86 - drbd devices are attached to a local disk/remote peer and made primary
88 A block device is identified by three items:
89 - the /dev path of the device (dynamic)
90 - a unique ID of the device (static)
91 - it's major/minor pair (dynamic)
93 Not all devices implement both the first two as distinct items. LVM
94 logical volumes have their unique ID (the pair volume group, logical
95 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
96 the /dev path is again dynamic and the unique id is the pair (host1,
99 You can get to a device in two ways:
100 - creating the (real) device, which returns you
101 an attached instance (lvcreate)
102 - attaching of a python instance to an existing (real) device
104 The second point, the attachement to a device, is different
105 depending on whether the device is assembled or not. At init() time,
106 we search for a device with the same unique_id as us. If found,
107 good. It also means that the device is already assembled. If not,
108 after assembly we'll have our correct major/minor.
111 def __init__(self, unique_id, children, size):
112 self._children = children
114 self.unique_id = unique_id
117 self.attached = False
121 """Assemble the device from its components.
123 Implementations of this method by child classes must ensure that:
124 - after the device has been assembled, it knows its major/minor
125 numbers; this allows other devices (usually parents) to probe
126 correctly for their children
127 - calling this method on an existing, in-use device is safe
128 - if the device is already configured (and in an OK state),
129 this method is idempotent
135 """Find a device which matches our config and attach to it.
138 raise NotImplementedError
141 """Notifies that the device will no longer be used for I/O.
144 raise NotImplementedError
147 def Create(cls, unique_id, children, size):
148 """Create the device.
150 If the device cannot be created, it will return None
151 instead. Error messages go to the logging system.
153 Note that for some devices, the unique_id is used, and for other,
154 the children. The idea is that these two, taken together, are
155 enough for both creation and assembly (later).
158 raise NotImplementedError
161 """Remove this device.
163 This makes sense only for some of the device types: LV and file
164 storeage. Also note that if the device can't attach, the removal
168 raise NotImplementedError
170 def Rename(self, new_id):
171 """Rename this device.
173 This may or may not make sense for a given device type.
176 raise NotImplementedError
178 def Open(self, force=False):
179 """Make the device ready for use.
181 This makes the device ready for I/O. For now, just the DRBD
184 The force parameter signifies that if the device has any kind of
185 --force thing, it should be used, we know what we are doing.
188 raise NotImplementedError
191 """Shut down the device, freeing its children.
193 This undoes the `Assemble()` work, except for the child
194 assembling; as such, the children on the device are still
195 assembled after this call.
198 raise NotImplementedError
200 def SetSyncSpeed(self, speed):
201 """Adjust the sync speed of the mirror.
203 In case this is not a mirroring device, this is no-op.
208 for child in self._children:
209 result = result and child.SetSyncSpeed(speed)
212 def GetSyncStatus(self):
213 """Returns the sync status of the device.
215 If this device is a mirroring device, this function returns the
216 status of the mirror.
218 If sync_percent is None, it means the device is not syncing.
220 If estimated_time is None, it means we can't estimate
221 the time needed, otherwise it's the time left in seconds.
223 If is_degraded is True, it means the device is missing
224 redundancy. This is usually a sign that something went wrong in
225 the device setup, if sync_percent is None.
227 The ldisk parameter represents the degradation of the local
228 data. This is only valid for some devices, the rest will always
229 return False (not degraded).
232 @return: (sync_percent, estimated_time, is_degraded, ldisk)
235 return None, None, False, False
238 def CombinedSyncStatus(self):
239 """Calculate the mirror status recursively for our children.
241 The return value is the same as for `GetSyncStatus()` except the
242 minimum percent and maximum time are calculated across our
246 min_percent, max_time, is_degraded, ldisk = self.GetSyncStatus()
248 for child in self._children:
249 c_percent, c_time, c_degraded, c_ldisk = child.GetSyncStatus()
250 if min_percent is None:
251 min_percent = c_percent
252 elif c_percent is not None:
253 min_percent = min(min_percent, c_percent)
256 elif c_time is not None:
257 max_time = max(max_time, c_time)
258 is_degraded = is_degraded or c_degraded
259 ldisk = ldisk or c_ldisk
260 return min_percent, max_time, is_degraded, ldisk
263 def SetInfo(self, text):
264 """Update metadata with info text.
266 Only supported for some device types.
269 for child in self._children:
272 def Grow(self, amount):
273 """Grow the block device.
275 @param amount: the amount (in mebibytes) to grow with
278 raise NotImplementedError
281 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
282 (self.__class__, self.unique_id, self._children,
283 self.major, self.minor, self.dev_path))
286 class LogicalVolume(BlockDev):
287 """Logical Volume block device.
290 def __init__(self, unique_id, children, size):
291 """Attaches to a LV device.
293 The unique_id is a tuple (vg_name, lv_name)
296 super(LogicalVolume, self).__init__(unique_id, children, size)
297 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
298 raise ValueError("Invalid configuration data %s" % str(unique_id))
299 self._vg_name, self._lv_name = unique_id
300 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
301 self._degraded = True
302 self.major = self.minor = None
306 def Create(cls, unique_id, children, size):
307 """Create a new logical volume.
310 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
311 raise errors.ProgrammerError("Invalid configuration data %s" %
313 vg_name, lv_name = unique_id
314 pvs_info = cls.GetPVInfo(vg_name)
316 _ThrowError("Can't compute PV info for vg %s", vg_name)
320 pvlist = [ pv[1] for pv in pvs_info ]
321 free_size = sum([ pv[0] for pv in pvs_info ])
322 current_pvs = len(pvlist)
323 stripes = min(current_pvs, constants.LVM_STRIPECOUNT)
325 # The size constraint should have been checked from the master before
326 # calling the create function.
328 _ThrowError("Not enough free space: required %s,"
329 " available %s", size, free_size)
330 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
331 # If the free space is not well distributed, we won't be able to
332 # create an optimally-striped volume; in that case, we want to try
333 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
335 for stripes_arg in range(stripes, 0, -1):
336 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
337 if not result.failed:
340 _ThrowError("LV create failed (%s): %s",
341 result.fail_reason, result.output)
342 return LogicalVolume(unique_id, children, size)
345 def GetPVInfo(vg_name):
346 """Get the free space info for PVs in a volume group.
348 @param vg_name: the volume group name
351 @return: list of tuples (free_space, name) with free_space in mebibytes
354 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
355 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
357 result = utils.RunCmd(command)
359 logging.error("Can't get the PV information: %s - %s",
360 result.fail_reason, result.output)
363 for line in result.stdout.splitlines():
364 fields = line.strip().split(':')
366 logging.error("Can't parse pvs output: line '%s'", line)
368 # skip over pvs from another vg or ones which are not allocatable
369 if fields[1] != vg_name or fields[3][0] != 'a':
371 data.append((float(fields[2]), fields[0]))
376 """Remove this logical volume.
379 if not self.minor and not self.Attach():
380 # the LV does not exist
382 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
383 (self._vg_name, self._lv_name)])
385 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
387 def Rename(self, new_id):
388 """Rename this logical volume.
391 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
392 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
393 new_vg, new_name = new_id
394 if new_vg != self._vg_name:
395 raise errors.ProgrammerError("Can't move a logical volume across"
396 " volume groups (from %s to to %s)" %
397 (self._vg_name, new_vg))
398 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
400 _ThrowError("Failed to rename the logical volume: %s", result.output)
401 self._lv_name = new_name
402 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
405 """Attach to an existing LV.
407 This method will try to see if an existing and active LV exists
408 which matches our name. If so, its major/minor will be
412 self.attached = False
413 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
414 "-olv_attr,lv_kernel_major,lv_kernel_minor",
417 logging.error("Can't find LV %s: %s, %s",
418 self.dev_path, result.fail_reason, result.output)
420 out = result.stdout.strip().rstrip(',')
423 logging.error("Can't parse LVS output, len(%s) != 3", str(out))
426 status, major, minor = out[:3]
428 logging.error("lvs lv_attr is not 6 characters (%s)", status)
434 except ValueError, err:
435 logging.error("lvs major/minor cannot be parsed: %s", str(err))
439 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
445 """Assemble the device.
447 We alway run `lvchange -ay` on the LV to ensure it's active before
448 use, as there were cases when xenvg was not active after boot
449 (also possibly after disk issues).
452 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
454 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
457 """Shutdown the device.
459 This is a no-op for the LV device type, as we don't deactivate the
465 def GetSyncStatus(self):
466 """Returns the sync status of the device.
468 If this device is a mirroring device, this function returns the
469 status of the mirror.
471 For logical volumes, sync_percent and estimated_time are always
472 None (no recovery in progress, as we don't handle the mirrored LV
473 case). The is_degraded parameter is the inverse of the ldisk
476 For the ldisk parameter, we check if the logical volume has the
477 'virtual' type, which means it's not backed by existing storage
478 anymore (read from it return I/O error). This happens after a
479 physical disk failure and subsequent 'vgreduce --removemissing' on
482 The status was already read in Attach, so we just return it.
485 @return: (sync_percent, estimated_time, is_degraded, ldisk)
488 return None, None, self._degraded, self._degraded
490 def Open(self, force=False):
491 """Make the device ready for I/O.
493 This is a no-op for the LV device type.
499 """Notifies that the device will no longer be used for I/O.
501 This is a no-op for the LV device type.
506 def Snapshot(self, size):
507 """Create a snapshot copy of an lvm block device.
510 snap_name = self._lv_name + ".snap"
512 # remove existing snapshot if found
513 snap = LogicalVolume((self._vg_name, snap_name), None, size)
514 _IgnoreError(snap.Remove)
516 pvs_info = self.GetPVInfo(self._vg_name)
518 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
521 free_size, pv_name = pvs_info[0]
523 _ThrowError("Not enough free space: required %s,"
524 " available %s", size, free_size)
526 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
527 "-n%s" % snap_name, self.dev_path])
529 _ThrowError("command: %s error: %s - %s",
530 result.cmd, result.fail_reason, result.output)
534 def SetInfo(self, text):
535 """Update metadata with info text.
538 BlockDev.SetInfo(self, text)
540 # Replace invalid characters
541 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
542 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
544 # Only up to 128 characters are allowed
547 result = utils.RunCmd(["lvchange", "--addtag", text,
550 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
553 def Grow(self, amount):
554 """Grow the logical volume.
557 # we try multiple algorithms since the 'best' ones might not have
558 # space available in the right place, but later ones might (since
559 # they have less constraints); also note that only recent LVM
561 for alloc_policy in "contiguous", "cling", "normal":
562 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
563 "-L", "+%dm" % amount, self.dev_path])
564 if not result.failed:
566 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
569 class DRBD8Status(object):
570 """A DRBD status representation class.
572 Note that this doesn't support unconfigured devices (cs:Unconfigured).
575 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
576 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
577 "\s+ds:([^/]+)/(\S+)\s+.*$")
578 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
579 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
581 CS_UNCONFIGURED = "Unconfigured"
582 CS_STANDALONE = "StandAlone"
583 CS_WFCONNECTION = "WFConnection"
584 CS_WFREPORTPARAMS = "WFReportParams"
585 CS_CONNECTED = "Connected"
586 CS_STARTINGSYNCS = "StartingSyncS"
587 CS_STARTINGSYNCT = "StartingSyncT"
588 CS_WFBITMAPS = "WFBitMapS"
589 CS_WFBITMAPT = "WFBitMapT"
590 CS_WFSYNCUUID = "WFSyncUUID"
591 CS_SYNCSOURCE = "SyncSource"
592 CS_SYNCTARGET = "SyncTarget"
593 CS_PAUSEDSYNCS = "PausedSyncS"
594 CS_PAUSEDSYNCT = "PausedSyncT"
595 CSET_SYNC = frozenset([
608 DS_DISKLESS = "Diskless"
609 DS_ATTACHING = "Attaching" # transient state
610 DS_FAILED = "Failed" # transient state, next: diskless
611 DS_NEGOTIATING = "Negotiating" # transient state
612 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
613 DS_OUTDATED = "Outdated"
614 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
615 DS_CONSISTENT = "Consistent"
616 DS_UPTODATE = "UpToDate" # normal state
618 RO_PRIMARY = "Primary"
619 RO_SECONDARY = "Secondary"
620 RO_UNKNOWN = "Unknown"
622 def __init__(self, procline):
623 u = self.UNCONF_RE.match(procline)
625 self.cstatus = self.CS_UNCONFIGURED
626 self.lrole = self.rrole = self.ldisk = self.rdisk = None
628 m = self.LINE_RE.match(procline)
630 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
631 self.cstatus = m.group(1)
632 self.lrole = m.group(2)
633 self.rrole = m.group(3)
634 self.ldisk = m.group(4)
635 self.rdisk = m.group(5)
637 # end reading of data from the LINE_RE or UNCONF_RE
639 self.is_standalone = self.cstatus == self.CS_STANDALONE
640 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
641 self.is_connected = self.cstatus == self.CS_CONNECTED
642 self.is_primary = self.lrole == self.RO_PRIMARY
643 self.is_secondary = self.lrole == self.RO_SECONDARY
644 self.peer_primary = self.rrole == self.RO_PRIMARY
645 self.peer_secondary = self.rrole == self.RO_SECONDARY
646 self.both_primary = self.is_primary and self.peer_primary
647 self.both_secondary = self.is_secondary and self.peer_secondary
649 self.is_diskless = self.ldisk == self.DS_DISKLESS
650 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
652 self.is_in_resync = self.cstatus in self.CSET_SYNC
653 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
655 m = self.SYNC_RE.match(procline)
657 self.sync_percent = float(m.group(1))
658 hours = int(m.group(2))
659 minutes = int(m.group(3))
660 seconds = int(m.group(4))
661 self.est_time = hours * 3600 + minutes * 60 + seconds
663 # we have (in this if branch) no percent information, but if
664 # we're resyncing we need to 'fake' a sync percent information,
665 # as this is how cmdlib determines if it makes sense to wait for
667 if self.is_in_resync:
668 self.sync_percent = 0
670 self.sync_percent = None
674 class BaseDRBD(BlockDev):
677 This class contains a few bits of common functionality between the
678 0.7 and 8.x versions of DRBD.
681 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
682 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
685 _ST_UNCONFIGURED = "Unconfigured"
686 _ST_WFCONNECTION = "WFConnection"
687 _ST_CONNECTED = "Connected"
689 _STATUS_FILE = "/proc/drbd"
692 def _GetProcData(filename=_STATUS_FILE):
693 """Return data from /proc/drbd.
697 stat = open(filename, "r")
699 data = stat.read().splitlines()
702 except EnvironmentError, err:
703 if err.errno == errno.ENOENT:
704 _ThrowError("The file %s cannot be opened, check if the module"
705 " is loaded (%s)", filename, str(err))
707 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
709 _ThrowError("Can't read any data from %s", filename)
713 def _MassageProcData(data):
714 """Transform the output of _GetProdData into a nicer form.
716 @return: a dictionary of minor: joined lines from /proc/drbd
720 lmatch = re.compile("^ *([0-9]+):.*$")
722 old_minor = old_line = None
724 lresult = lmatch.match(line)
725 if lresult is not None:
726 if old_minor is not None:
727 results[old_minor] = old_line
728 old_minor = int(lresult.group(1))
731 if old_minor is not None:
732 old_line += " " + line.strip()
734 if old_minor is not None:
735 results[old_minor] = old_line
739 def _GetVersion(cls):
740 """Return the DRBD version.
742 This will return a dict with keys:
748 - proto2 (only on drbd > 8.2.X)
751 proc_data = cls._GetProcData()
752 first_line = proc_data[0].strip()
753 version = cls._VERSION_RE.match(first_line)
755 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
758 values = version.groups()
759 retval = {'k_major': int(values[0]),
760 'k_minor': int(values[1]),
761 'k_point': int(values[2]),
762 'api': int(values[3]),
763 'proto': int(values[4]),
765 if values[5] is not None:
766 retval['proto2'] = values[5]
772 """Return the path to a drbd device for a given minor.
775 return "/dev/drbd%d" % minor
778 def GetUsedDevs(cls):
779 """Compute the list of used DRBD devices.
782 data = cls._GetProcData()
785 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
787 match = valid_line.match(line)
790 minor = int(match.group(1))
791 state = match.group(2)
792 if state == cls._ST_UNCONFIGURED:
794 used_devs[minor] = state, line
798 def _SetFromMinor(self, minor):
799 """Set our parameters based on the given minor.
801 This sets our minor variable and our dev_path.
805 self.minor = self.dev_path = None
806 self.attached = False
809 self.dev_path = self._DevPath(minor)
813 def _CheckMetaSize(meta_device):
814 """Check if the given meta device looks like a valid one.
816 This currently only check the size, which must be around
820 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
822 _ThrowError("Failed to get device size: %s - %s",
823 result.fail_reason, result.output)
825 sectors = int(result.stdout)
827 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
828 bytes = sectors * 512
829 if bytes < 128 * 1024 * 1024: # less than 128MiB
830 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
831 # the maximum *valid* size of the meta device when living on top
832 # of LVM is hard to compute: it depends on the number of stripes
833 # and the PE size; e.g. a 2-stripe, 64MB PE will result in a 128MB
834 # (normal size), but an eight-stripe 128MB PE will result in a 1GB
835 # size meta device; as such, we restrict it to 1GB (a little bit
836 # too generous, but making assumptions about PE size is hard)
837 if bytes > 1024 * 1024 * 1024:
838 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
840 def Rename(self, new_id):
843 This is not supported for drbd devices.
846 raise errors.ProgrammerError("Can't rename a drbd device")
849 class DRBD8(BaseDRBD):
850 """DRBD v8.x block device.
852 This implements the local host part of the DRBD device, i.e. it
853 doesn't do anything to the supposed peer. If you need a fully
854 connected DRBD pair, you need to use this class on both hosts.
856 The unique_id for the drbd device is the (local_ip, local_port,
857 remote_ip, remote_port) tuple, and it must have two children: the
858 data device and the meta_device. The meta device is checked for
859 valid size and is zeroed on create.
866 _NET_RECONFIG_TIMEOUT = 60
868 def __init__(self, unique_id, children, size):
869 if children and children.count(None) > 0:
871 super(DRBD8, self).__init__(unique_id, children, size)
872 self.major = self._DRBD_MAJOR
873 version = self._GetVersion()
874 if version['k_major'] != 8 :
875 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
876 " usage: kernel is %s.%s, ganeti wants 8.x",
877 version['k_major'], version['k_minor'])
879 if len(children) not in (0, 2):
880 raise ValueError("Invalid configuration data %s" % str(children))
881 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
882 raise ValueError("Invalid configuration data %s" % str(unique_id))
883 (self._lhost, self._lport,
884 self._rhost, self._rport,
885 self._aminor, self._secret) = unique_id
886 if (self._lhost is not None and self._lhost == self._rhost and
887 self._lport == self._rport):
888 raise ValueError("Invalid configuration data, same local/remote %s" %
893 def _InitMeta(cls, minor, dev_path):
894 """Initialize a meta device.
896 This will not work if the given minor is in use.
899 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
900 "v08", dev_path, "0", "create-md"])
902 _ThrowError("Can't initialize meta device: %s", result.output)
905 def _FindUnusedMinor(cls):
906 """Find an unused DRBD device.
908 This is specific to 8.x as the minors are allocated dynamically,
909 so non-existing numbers up to a max minor count are actually free.
912 data = cls._GetProcData()
914 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
915 used_line = re.compile("^ *([0-9]+): cs:")
918 match = unused_line.match(line)
920 return int(match.group(1))
921 match = used_line.match(line)
923 minor = int(match.group(1))
924 highest = max(highest, minor)
925 if highest is None: # there are no minors in use at all
927 if highest >= cls._MAX_MINORS:
928 logging.error("Error: no free drbd minors!")
929 raise errors.BlockDeviceError("Can't find a free DRBD minor")
933 def _GetShowParser(cls):
934 """Return a parser for `drbd show` output.
936 This will either create or return an already-create parser for the
937 output of the command `drbd show`.
940 if cls._PARSE_SHOW is not None:
941 return cls._PARSE_SHOW
944 lbrace = pyp.Literal("{").suppress()
945 rbrace = pyp.Literal("}").suppress()
946 semi = pyp.Literal(";").suppress()
947 # this also converts the value to an int
948 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
950 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
951 defa = pyp.Literal("_is_default").suppress()
952 dbl_quote = pyp.Literal('"').suppress()
954 keyword = pyp.Word(pyp.alphanums + '-')
957 value = pyp.Word(pyp.alphanums + '_-/.:')
958 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
959 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
960 pyp.Optional(pyp.Literal("ipv6")).suppress())
961 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
962 pyp.Literal(':').suppress() + number)
963 # meta device, extended syntax
964 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
965 number + pyp.Word(']').suppress())
966 # device name, extended syntax
967 device_value = pyp.Literal("minor").suppress() + number
970 stmt = (~rbrace + keyword + ~lbrace +
971 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
973 pyp.Optional(defa) + semi +
974 pyp.Optional(pyp.restOfLine).suppress())
977 section_name = pyp.Word(pyp.alphas + '_')
978 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
980 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
983 cls._PARSE_SHOW = bnf
988 def _GetShowData(cls, minor):
989 """Return the `drbdsetup show` data for a minor.
992 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
994 logging.error("Can't display the drbd config: %s - %s",
995 result.fail_reason, result.output)
1000 def _GetDevInfo(cls, out):
1001 """Parse details about a given DRBD minor.
1003 This return, if available, the local backing device (as a path)
1004 and the local and remote (ip, port) information from a string
1005 containing the output of the `drbdsetup show` command as returned
1013 bnf = cls._GetShowParser()
1017 results = bnf.parseString(out)
1018 except pyp.ParseException, err:
1019 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1021 # and massage the results into our desired format
1022 for section in results:
1024 if sname == "_this_host":
1025 for lst in section[1:]:
1026 if lst[0] == "disk":
1027 data["local_dev"] = lst[1]
1028 elif lst[0] == "meta-disk":
1029 data["meta_dev"] = lst[1]
1030 data["meta_index"] = lst[2]
1031 elif lst[0] == "address":
1032 data["local_addr"] = tuple(lst[1:])
1033 elif sname == "_remote_host":
1034 for lst in section[1:]:
1035 if lst[0] == "address":
1036 data["remote_addr"] = tuple(lst[1:])
1039 def _MatchesLocal(self, info):
1040 """Test if our local config matches with an existing device.
1042 The parameter should be as returned from `_GetDevInfo()`. This
1043 method tests if our local backing device is the same as the one in
1044 the info parameter, in effect testing if we look like the given
1049 backend, meta = self._children
1051 backend = meta = None
1053 if backend is not None:
1054 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1056 retval = ("local_dev" not in info)
1058 if meta is not None:
1059 retval = retval and ("meta_dev" in info and
1060 info["meta_dev"] == meta.dev_path)
1061 retval = retval and ("meta_index" in info and
1062 info["meta_index"] == 0)
1064 retval = retval and ("meta_dev" not in info and
1065 "meta_index" not in info)
1068 def _MatchesNet(self, info):
1069 """Test if our network config matches with an existing device.
1071 The parameter should be as returned from `_GetDevInfo()`. This
1072 method tests if our network configuration is the same as the one
1073 in the info parameter, in effect testing if we look like the given
1077 if (((self._lhost is None and not ("local_addr" in info)) and
1078 (self._rhost is None and not ("remote_addr" in info)))):
1081 if self._lhost is None:
1084 if not ("local_addr" in info and
1085 "remote_addr" in info):
1088 retval = (info["local_addr"] == (self._lhost, self._lport))
1089 retval = (retval and
1090 info["remote_addr"] == (self._rhost, self._rport))
1094 def _AssembleLocal(cls, minor, backend, meta, size):
1095 """Configure the local part of a DRBD device.
1098 args = ["drbdsetup", cls._DevPath(minor), "disk",
1103 result = utils.RunCmd(args)
1105 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1108 def _AssembleNet(cls, minor, net_info, protocol,
1109 dual_pri=False, hmac=None, secret=None):
1110 """Configure the network part of the device.
1113 lhost, lport, rhost, rport = net_info
1114 if None in net_info:
1115 # we don't want network connection and actually want to make
1117 cls._ShutdownNet(minor)
1120 # Workaround for a race condition. When DRBD is doing its dance to
1121 # establish a connection with its peer, it also sends the
1122 # synchronization speed over the wire. In some cases setting the
1123 # sync speed only after setting up both sides can race with DRBD
1124 # connecting, hence we set it here before telling DRBD anything
1126 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1128 args = ["drbdsetup", cls._DevPath(minor), "net",
1129 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1130 "-A", "discard-zero-changes",
1137 args.extend(["-a", hmac, "-x", secret])
1138 result = utils.RunCmd(args)
1140 _ThrowError("drbd%d: can't setup network: %s - %s",
1141 minor, result.fail_reason, result.output)
1143 timeout = time.time() + 10
1145 while time.time() < timeout:
1146 info = cls._GetDevInfo(cls._GetShowData(minor))
1147 if not "local_addr" in info or not "remote_addr" in info:
1150 if (info["local_addr"] != (lhost, lport) or
1151 info["remote_addr"] != (rhost, rport)):
1157 _ThrowError("drbd%d: timeout while configuring network", minor)
1159 def AddChildren(self, devices):
1160 """Add a disk to the DRBD device.
1163 if self.minor is None:
1164 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1166 if len(devices) != 2:
1167 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1168 info = self._GetDevInfo(self._GetShowData(self.minor))
1169 if "local_dev" in info:
1170 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1171 backend, meta = devices
1172 if backend.dev_path is None or meta.dev_path is None:
1173 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1176 self._CheckMetaSize(meta.dev_path)
1177 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1179 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1180 self._children = devices
1182 def RemoveChildren(self, devices):
1183 """Detach the drbd device from local storage.
1186 if self.minor is None:
1187 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1189 # early return if we don't actually have backing storage
1190 info = self._GetDevInfo(self._GetShowData(self.minor))
1191 if "local_dev" not in info:
1193 if len(self._children) != 2:
1194 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1196 if self._children.count(None) == 2: # we don't actually have children :)
1197 logging.warning("drbd%d: requested detach while detached", self.minor)
1199 if len(devices) != 2:
1200 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1201 for child, dev in zip(self._children, devices):
1202 if dev != child.dev_path:
1203 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1204 " RemoveChildren", self.minor, dev, child.dev_path)
1206 self._ShutdownLocal(self.minor)
1210 def _SetMinorSyncSpeed(cls, minor, kbytes):
1211 """Set the speed of the DRBD syncer.
1213 This is the low-level implementation.
1216 @param minor: the drbd minor whose settings we change
1218 @param kbytes: the speed in kbytes/second
1220 @return: the success of the operation
1223 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1224 "-r", "%d" % kbytes, "--create-device"])
1226 logging.error("Can't change syncer rate: %s - %s",
1227 result.fail_reason, result.output)
1228 return not result.failed
1230 def SetSyncSpeed(self, kbytes):
1231 """Set the speed of the DRBD syncer.
1234 @param kbytes: the speed in kbytes/second
1236 @return: the success of the operation
1239 if self.minor is None:
1240 logging.info("Not attached during SetSyncSpeed")
1242 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1243 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1245 def GetProcStatus(self):
1246 """Return device data from /proc.
1249 if self.minor is None:
1250 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1251 proc_info = self._MassageProcData(self._GetProcData())
1252 if self.minor not in proc_info:
1253 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1254 return DRBD8Status(proc_info[self.minor])
1256 def GetSyncStatus(self):
1257 """Returns the sync status of the device.
1260 If sync_percent is None, it means all is ok
1261 If estimated_time is None, it means we can't esimate
1262 the time needed, otherwise it's the time left in seconds.
1265 We set the is_degraded parameter to True on two conditions:
1266 network not connected or local disk missing.
1268 We compute the ldisk parameter based on wheter we have a local
1272 @return: (sync_percent, estimated_time, is_degraded, ldisk)
1275 if self.minor is None and not self.Attach():
1276 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1277 stats = self.GetProcStatus()
1278 ldisk = not stats.is_disk_uptodate
1279 is_degraded = not stats.is_connected
1280 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1282 def Open(self, force=False):
1283 """Make the local state primary.
1285 If the 'force' parameter is given, the '-o' option is passed to
1286 drbdsetup. Since this is a potentially dangerous operation, the
1287 force flag should be only given after creation, when it actually
1291 if self.minor is None and not self.Attach():
1292 logging.error("DRBD cannot attach to a device during open")
1294 cmd = ["drbdsetup", self.dev_path, "primary"]
1297 result = utils.RunCmd(cmd)
1299 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1303 """Make the local state secondary.
1305 This will, of course, fail if the device is in use.
1308 if self.minor is None and not self.Attach():
1309 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1310 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1312 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1313 self.minor, result.output)
1315 def DisconnectNet(self):
1316 """Removes network configuration.
1318 This method shutdowns the network side of the device.
1320 The method will wait up to a hardcoded timeout for the device to
1321 go into standalone after the 'disconnect' command before
1322 re-configuring it, as sometimes it takes a while for the
1323 disconnect to actually propagate and thus we might issue a 'net'
1324 command while the device is still connected. If the device will
1325 still be attached to the network and we time out, we raise an
1329 if self.minor is None:
1330 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1332 if None in (self._lhost, self._lport, self._rhost, self._rport):
1333 _ThrowError("drbd%d: DRBD disk missing network info in"
1334 " DisconnectNet()", self.minor)
1336 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1337 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1338 sleep_time = 0.100 # we start the retry time at 100 miliseconds
1339 while time.time() < timeout_limit:
1340 status = self.GetProcStatus()
1341 if status.is_standalone:
1343 # retry the disconnect, it seems possible that due to a
1344 # well-time disconnect on the peer, my disconnect command might
1345 # be ingored and forgotten
1346 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1348 time.sleep(sleep_time)
1349 sleep_time = min(2, sleep_time * 1.5)
1351 if not status.is_standalone:
1352 if ever_disconnected:
1353 msg = ("drbd%d: device did not react to the"
1354 " 'disconnect' command in a timely manner")
1356 msg = "drbd%d: can't shutdown network, even after multiple retries"
1357 _ThrowError(msg, self.minor)
1359 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1360 if reconfig_time > 15: # hardcoded alert limit
1361 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1362 self.minor, reconfig_time)
1364 def AttachNet(self, multimaster):
1365 """Reconnects the network.
1367 This method connects the network side of the device with a
1368 specified multi-master flag. The device needs to be 'Standalone'
1369 but have valid network configuration data.
1372 - multimaster: init the network in dual-primary mode
1375 if self.minor is None:
1376 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1378 if None in (self._lhost, self._lport, self._rhost, self._rport):
1379 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1381 status = self.GetProcStatus()
1383 if not status.is_standalone:
1384 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1386 self._AssembleNet(self.minor,
1387 (self._lhost, self._lport, self._rhost, self._rport),
1388 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1389 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1392 """Check if our minor is configured.
1394 This doesn't do any device configurations - it only checks if the
1395 minor is in a state different from Unconfigured.
1397 Note that this function will not change the state of the system in
1398 any way (except in case of side-effects caused by reading from
1402 used_devs = self.GetUsedDevs()
1403 if self._aminor in used_devs:
1404 minor = self._aminor
1408 self._SetFromMinor(minor)
1409 return minor is not None
1412 """Assemble the drbd.
1415 - if we have a configured device, we try to ensure that it matches
1417 - if not, we create it from zero
1420 super(DRBD8, self).Assemble()
1423 if self.minor is None:
1424 # local device completely unconfigured
1425 self._FastAssemble()
1427 # we have to recheck the local and network status and try to fix
1429 self._SlowAssemble()
1431 def _SlowAssemble(self):
1432 """Assembles the DRBD device from a (partially) configured device.
1434 In case of partially attached (local device matches but no network
1435 setup), we perform the network attach. If successful, we re-test
1436 the attach if can return success.
1439 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1440 for minor in (self._aminor,):
1441 info = self._GetDevInfo(self._GetShowData(minor))
1442 match_l = self._MatchesLocal(info)
1443 match_r = self._MatchesNet(info)
1445 if match_l and match_r:
1446 # everything matches
1449 if match_l and not match_r and "local_addr" not in info:
1450 # disk matches, but not attached to network, attach and recheck
1451 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1452 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1453 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1456 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1457 " show' disagrees", minor)
1459 if match_r and "local_dev" not in info:
1460 # no local disk, but network attached and it matches
1461 self._AssembleLocal(minor, self._children[0].dev_path,
1462 self._children[1].dev_path, self.size)
1463 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1466 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1467 " show' disagrees", minor)
1469 # this case must be considered only if we actually have local
1470 # storage, i.e. not in diskless mode, because all diskless
1471 # devices are equal from the point of view of local
1473 if (match_l and "local_dev" in info and
1474 not match_r and "local_addr" in info):
1475 # strange case - the device network part points to somewhere
1476 # else, even though its local storage is ours; as we own the
1477 # drbd space, we try to disconnect from the remote peer and
1478 # reconnect to our correct one
1480 self._ShutdownNet(minor)
1481 except errors.BlockDeviceError, err:
1482 _ThrowError("drbd%d: device has correct local storage, wrong"
1483 " remote peer and is unable to disconnect in order"
1484 " to attach to the correct peer: %s", minor, str(err))
1485 # note: _AssembleNet also handles the case when we don't want
1486 # local storage (i.e. one or more of the _[lr](host|port) is
1488 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1489 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1490 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1493 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1494 " show' disagrees", minor)
1499 self._SetFromMinor(minor)
1501 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1504 def _FastAssemble(self):
1505 """Assemble the drbd device from zero.
1507 This is run when in Assemble we detect our minor is unused.
1510 minor = self._aminor
1511 if self._children and self._children[0] and self._children[1]:
1512 self._AssembleLocal(minor, self._children[0].dev_path,
1513 self._children[1].dev_path, self.size)
1514 if self._lhost and self._lport and self._rhost and self._rport:
1515 self._AssembleNet(minor,
1516 (self._lhost, self._lport, self._rhost, self._rport),
1517 constants.DRBD_NET_PROTOCOL,
1518 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1519 self._SetFromMinor(minor)
1522 def _ShutdownLocal(cls, minor):
1523 """Detach from the local device.
1525 I/Os will continue to be served from the remote device. If we
1526 don't have a remote device, this operation will fail.
1529 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1531 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1534 def _ShutdownNet(cls, minor):
1535 """Disconnect from the remote peer.
1537 This fails if we don't have a local device.
1540 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1542 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1545 def _ShutdownAll(cls, minor):
1546 """Deactivate the device.
1548 This will, of course, fail if the device is in use.
1551 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1553 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1554 minor, result.output)
1557 """Shutdown the DRBD device.
1560 if self.minor is None and not self.Attach():
1561 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1565 self.dev_path = None
1566 self._ShutdownAll(minor)
1569 """Stub remove for DRBD devices.
1575 def Create(cls, unique_id, children, size):
1576 """Create a new DRBD8 device.
1578 Since DRBD devices are not created per se, just assembled, this
1579 function only initializes the metadata.
1582 if len(children) != 2:
1583 raise errors.ProgrammerError("Invalid setup for the drbd device")
1584 # check that the minor is unused
1585 aminor = unique_id[4]
1586 proc_info = cls._MassageProcData(cls._GetProcData())
1587 if aminor in proc_info:
1588 status = DRBD8Status(proc_info[aminor])
1589 in_use = status.is_in_use
1593 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1596 if not meta.Attach():
1597 _ThrowError("drbd%d: can't attach to meta device '%s'",
1599 cls._CheckMetaSize(meta.dev_path)
1600 cls._InitMeta(aminor, meta.dev_path)
1601 return cls(unique_id, children, size)
1603 def Grow(self, amount):
1604 """Resize the DRBD device and its backing storage.
1607 if self.minor is None:
1608 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1609 if len(self._children) != 2 or None in self._children:
1610 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1611 self._children[0].Grow(amount)
1612 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1614 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1617 class FileStorage(BlockDev):
1620 This class represents the a file storage backend device.
1622 The unique_id for the file device is a (file_driver, file_path) tuple.
1625 def __init__(self, unique_id, children, size):
1626 """Initalizes a file device backend.
1630 raise errors.BlockDeviceError("Invalid setup for file device")
1631 super(FileStorage, self).__init__(unique_id, children, size)
1632 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1633 raise ValueError("Invalid configuration data %s" % str(unique_id))
1634 self.driver = unique_id[0]
1635 self.dev_path = unique_id[1]
1639 """Assemble the device.
1641 Checks whether the file device exists, raises BlockDeviceError otherwise.
1644 if not os.path.exists(self.dev_path):
1645 _ThrowError("File device '%s' does not exist" % self.dev_path)
1648 """Shutdown the device.
1650 This is a no-op for the file type, as we don't deacivate
1651 the file on shutdown.
1656 def Open(self, force=False):
1657 """Make the device ready for I/O.
1659 This is a no-op for the file type.
1665 """Notifies that the device will no longer be used for I/O.
1667 This is a no-op for the file type.
1673 """Remove the file backing the block device.
1676 @return: True if the removal was successful
1680 os.remove(self.dev_path)
1681 except OSError, err:
1682 if err.errno != errno.ENOENT:
1683 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1686 """Attach to an existing file.
1688 Check if this file already exists.
1691 @return: True if file exists
1694 self.attached = os.path.exists(self.dev_path)
1695 return self.attached
1698 def Create(cls, unique_id, children, size):
1699 """Create a new file.
1701 @param size: the size of file in MiB
1703 @rtype: L{bdev.FileStorage}
1704 @return: an instance of FileStorage
1707 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1708 raise ValueError("Invalid configuration data %s" % str(unique_id))
1709 dev_path = unique_id[1]
1710 if os.path.exists(dev_path):
1711 _ThrowError("File already existing: %s", dev_path)
1713 f = open(dev_path, 'w')
1714 f.truncate(size * 1024 * 1024)
1716 except IOError, err:
1717 _ThrowError("Error in file creation: %", str(err))
1719 return FileStorage(unique_id, children, size)
1723 constants.LD_LV: LogicalVolume,
1724 constants.LD_DRBD8: DRBD8,
1725 constants.LD_FILE: FileStorage,
1729 def FindDevice(dev_type, unique_id, children, size):
1730 """Search for an existing, assembled device.
1732 This will succeed only if the device exists and is assembled, but it
1733 does not do any actions in order to activate the device.
1736 if dev_type not in DEV_MAP:
1737 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1738 device = DEV_MAP[dev_type](unique_id, children, size)
1739 if not device.attached:
1744 def Assemble(dev_type, unique_id, children, size):
1745 """Try to attach or assemble an existing device.
1747 This will attach to assemble the device, as needed, to bring it
1748 fully up. It must be safe to run on already-assembled devices.
1751 if dev_type not in DEV_MAP:
1752 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1753 device = DEV_MAP[dev_type](unique_id, children, size)
1758 def Create(dev_type, unique_id, children, size):
1762 if dev_type not in DEV_MAP:
1763 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1764 device = DEV_MAP[dev_type].Create(unique_id, children, size)