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 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
832 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
834 def Rename(self, new_id):
837 This is not supported for drbd devices.
840 raise errors.ProgrammerError("Can't rename a drbd device")
843 class DRBD8(BaseDRBD):
844 """DRBD v8.x block device.
846 This implements the local host part of the DRBD device, i.e. it
847 doesn't do anything to the supposed peer. If you need a fully
848 connected DRBD pair, you need to use this class on both hosts.
850 The unique_id for the drbd device is the (local_ip, local_port,
851 remote_ip, remote_port) tuple, and it must have two children: the
852 data device and the meta_device. The meta device is checked for
853 valid size and is zeroed on create.
860 _NET_RECONFIG_TIMEOUT = 60
862 def __init__(self, unique_id, children, size):
863 if children and children.count(None) > 0:
865 super(DRBD8, self).__init__(unique_id, children, size)
866 self.major = self._DRBD_MAJOR
867 version = self._GetVersion()
868 if version['k_major'] != 8 :
869 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
870 " usage: kernel is %s.%s, ganeti wants 8.x",
871 version['k_major'], version['k_minor'])
873 if len(children) not in (0, 2):
874 raise ValueError("Invalid configuration data %s" % str(children))
875 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
876 raise ValueError("Invalid configuration data %s" % str(unique_id))
877 (self._lhost, self._lport,
878 self._rhost, self._rport,
879 self._aminor, self._secret) = unique_id
880 if (self._lhost is not None and self._lhost == self._rhost and
881 self._lport == self._rport):
882 raise ValueError("Invalid configuration data, same local/remote %s" %
887 def _InitMeta(cls, minor, dev_path):
888 """Initialize a meta device.
890 This will not work if the given minor is in use.
893 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
894 "v08", dev_path, "0", "create-md"])
896 _ThrowError("Can't initialize meta device: %s", result.output)
899 def _FindUnusedMinor(cls):
900 """Find an unused DRBD device.
902 This is specific to 8.x as the minors are allocated dynamically,
903 so non-existing numbers up to a max minor count are actually free.
906 data = cls._GetProcData()
908 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
909 used_line = re.compile("^ *([0-9]+): cs:")
912 match = unused_line.match(line)
914 return int(match.group(1))
915 match = used_line.match(line)
917 minor = int(match.group(1))
918 highest = max(highest, minor)
919 if highest is None: # there are no minors in use at all
921 if highest >= cls._MAX_MINORS:
922 logging.error("Error: no free drbd minors!")
923 raise errors.BlockDeviceError("Can't find a free DRBD minor")
927 def _GetShowParser(cls):
928 """Return a parser for `drbd show` output.
930 This will either create or return an already-create parser for the
931 output of the command `drbd show`.
934 if cls._PARSE_SHOW is not None:
935 return cls._PARSE_SHOW
938 lbrace = pyp.Literal("{").suppress()
939 rbrace = pyp.Literal("}").suppress()
940 semi = pyp.Literal(";").suppress()
941 # this also converts the value to an int
942 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
944 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
945 defa = pyp.Literal("_is_default").suppress()
946 dbl_quote = pyp.Literal('"').suppress()
948 keyword = pyp.Word(pyp.alphanums + '-')
951 value = pyp.Word(pyp.alphanums + '_-/.:')
952 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
953 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
954 pyp.Optional(pyp.Literal("ipv6")).suppress())
955 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
956 pyp.Literal(':').suppress() + number)
957 # meta device, extended syntax
958 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
959 number + pyp.Word(']').suppress())
960 # device name, extended syntax
961 device_value = pyp.Literal("minor").suppress() + number
964 stmt = (~rbrace + keyword + ~lbrace +
965 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
967 pyp.Optional(defa) + semi +
968 pyp.Optional(pyp.restOfLine).suppress())
971 section_name = pyp.Word(pyp.alphas + '_')
972 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
974 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
977 cls._PARSE_SHOW = bnf
982 def _GetShowData(cls, minor):
983 """Return the `drbdsetup show` data for a minor.
986 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
988 logging.error("Can't display the drbd config: %s - %s",
989 result.fail_reason, result.output)
994 def _GetDevInfo(cls, out):
995 """Parse details about a given DRBD minor.
997 This return, if available, the local backing device (as a path)
998 and the local and remote (ip, port) information from a string
999 containing the output of the `drbdsetup show` command as returned
1007 bnf = cls._GetShowParser()
1011 results = bnf.parseString(out)
1012 except pyp.ParseException, err:
1013 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1015 # and massage the results into our desired format
1016 for section in results:
1018 if sname == "_this_host":
1019 for lst in section[1:]:
1020 if lst[0] == "disk":
1021 data["local_dev"] = lst[1]
1022 elif lst[0] == "meta-disk":
1023 data["meta_dev"] = lst[1]
1024 data["meta_index"] = lst[2]
1025 elif lst[0] == "address":
1026 data["local_addr"] = tuple(lst[1:])
1027 elif sname == "_remote_host":
1028 for lst in section[1:]:
1029 if lst[0] == "address":
1030 data["remote_addr"] = tuple(lst[1:])
1033 def _MatchesLocal(self, info):
1034 """Test if our local config matches with an existing device.
1036 The parameter should be as returned from `_GetDevInfo()`. This
1037 method tests if our local backing device is the same as the one in
1038 the info parameter, in effect testing if we look like the given
1043 backend, meta = self._children
1045 backend = meta = None
1047 if backend is not None:
1048 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1050 retval = ("local_dev" not in info)
1052 if meta is not None:
1053 retval = retval and ("meta_dev" in info and
1054 info["meta_dev"] == meta.dev_path)
1055 retval = retval and ("meta_index" in info and
1056 info["meta_index"] == 0)
1058 retval = retval and ("meta_dev" not in info and
1059 "meta_index" not in info)
1062 def _MatchesNet(self, info):
1063 """Test if our network config matches with an existing device.
1065 The parameter should be as returned from `_GetDevInfo()`. This
1066 method tests if our network configuration is the same as the one
1067 in the info parameter, in effect testing if we look like the given
1071 if (((self._lhost is None and not ("local_addr" in info)) and
1072 (self._rhost is None and not ("remote_addr" in info)))):
1075 if self._lhost is None:
1078 if not ("local_addr" in info and
1079 "remote_addr" in info):
1082 retval = (info["local_addr"] == (self._lhost, self._lport))
1083 retval = (retval and
1084 info["remote_addr"] == (self._rhost, self._rport))
1088 def _AssembleLocal(cls, minor, backend, meta, size):
1089 """Configure the local part of a DRBD device.
1092 args = ["drbdsetup", cls._DevPath(minor), "disk",
1097 result = utils.RunCmd(args)
1099 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1102 def _AssembleNet(cls, minor, net_info, protocol,
1103 dual_pri=False, hmac=None, secret=None):
1104 """Configure the network part of the device.
1107 lhost, lport, rhost, rport = net_info
1108 if None in net_info:
1109 # we don't want network connection and actually want to make
1111 cls._ShutdownNet(minor)
1114 # Workaround for a race condition. When DRBD is doing its dance to
1115 # establish a connection with its peer, it also sends the
1116 # synchronization speed over the wire. In some cases setting the
1117 # sync speed only after setting up both sides can race with DRBD
1118 # connecting, hence we set it here before telling DRBD anything
1120 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1122 args = ["drbdsetup", cls._DevPath(minor), "net",
1123 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1124 "-A", "discard-zero-changes",
1131 args.extend(["-a", hmac, "-x", secret])
1132 result = utils.RunCmd(args)
1134 _ThrowError("drbd%d: can't setup network: %s - %s",
1135 minor, result.fail_reason, result.output)
1137 timeout = time.time() + 10
1139 while time.time() < timeout:
1140 info = cls._GetDevInfo(cls._GetShowData(minor))
1141 if not "local_addr" in info or not "remote_addr" in info:
1144 if (info["local_addr"] != (lhost, lport) or
1145 info["remote_addr"] != (rhost, rport)):
1151 _ThrowError("drbd%d: timeout while configuring network", minor)
1153 def AddChildren(self, devices):
1154 """Add a disk to the DRBD device.
1157 if self.minor is None:
1158 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1160 if len(devices) != 2:
1161 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1162 info = self._GetDevInfo(self._GetShowData(self.minor))
1163 if "local_dev" in info:
1164 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1165 backend, meta = devices
1166 if backend.dev_path is None or meta.dev_path is None:
1167 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1170 self._CheckMetaSize(meta.dev_path)
1171 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1173 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1174 self._children = devices
1176 def RemoveChildren(self, devices):
1177 """Detach the drbd device from local storage.
1180 if self.minor is None:
1181 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1183 # early return if we don't actually have backing storage
1184 info = self._GetDevInfo(self._GetShowData(self.minor))
1185 if "local_dev" not in info:
1187 if len(self._children) != 2:
1188 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1190 if self._children.count(None) == 2: # we don't actually have children :)
1191 logging.warning("drbd%d: requested detach while detached", self.minor)
1193 if len(devices) != 2:
1194 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1195 for child, dev in zip(self._children, devices):
1196 if dev != child.dev_path:
1197 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1198 " RemoveChildren", self.minor, dev, child.dev_path)
1200 self._ShutdownLocal(self.minor)
1204 def _SetMinorSyncSpeed(cls, minor, kbytes):
1205 """Set the speed of the DRBD syncer.
1207 This is the low-level implementation.
1210 @param minor: the drbd minor whose settings we change
1212 @param kbytes: the speed in kbytes/second
1214 @return: the success of the operation
1217 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1218 "-r", "%d" % kbytes, "--create-device"])
1220 logging.error("Can't change syncer rate: %s - %s",
1221 result.fail_reason, result.output)
1222 return not result.failed
1224 def SetSyncSpeed(self, kbytes):
1225 """Set the speed of the DRBD syncer.
1228 @param kbytes: the speed in kbytes/second
1230 @return: the success of the operation
1233 if self.minor is None:
1234 logging.info("Not attached during SetSyncSpeed")
1236 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1237 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1239 def GetProcStatus(self):
1240 """Return device data from /proc.
1243 if self.minor is None:
1244 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1245 proc_info = self._MassageProcData(self._GetProcData())
1246 if self.minor not in proc_info:
1247 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1248 return DRBD8Status(proc_info[self.minor])
1250 def GetSyncStatus(self):
1251 """Returns the sync status of the device.
1254 If sync_percent is None, it means all is ok
1255 If estimated_time is None, it means we can't esimate
1256 the time needed, otherwise it's the time left in seconds.
1259 We set the is_degraded parameter to True on two conditions:
1260 network not connected or local disk missing.
1262 We compute the ldisk parameter based on wheter we have a local
1266 @return: (sync_percent, estimated_time, is_degraded, ldisk)
1269 if self.minor is None and not self.Attach():
1270 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1271 stats = self.GetProcStatus()
1272 ldisk = not stats.is_disk_uptodate
1273 is_degraded = not stats.is_connected
1274 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1276 def Open(self, force=False):
1277 """Make the local state primary.
1279 If the 'force' parameter is given, the '-o' option is passed to
1280 drbdsetup. Since this is a potentially dangerous operation, the
1281 force flag should be only given after creation, when it actually
1285 if self.minor is None and not self.Attach():
1286 logging.error("DRBD cannot attach to a device during open")
1288 cmd = ["drbdsetup", self.dev_path, "primary"]
1291 result = utils.RunCmd(cmd)
1293 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1297 """Make the local state secondary.
1299 This will, of course, fail if the device is in use.
1302 if self.minor is None and not self.Attach():
1303 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1304 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1306 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1307 self.minor, result.output)
1309 def DisconnectNet(self):
1310 """Removes network configuration.
1312 This method shutdowns the network side of the device.
1314 The method will wait up to a hardcoded timeout for the device to
1315 go into standalone after the 'disconnect' command before
1316 re-configuring it, as sometimes it takes a while for the
1317 disconnect to actually propagate and thus we might issue a 'net'
1318 command while the device is still connected. If the device will
1319 still be attached to the network and we time out, we raise an
1323 if self.minor is None:
1324 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1326 if None in (self._lhost, self._lport, self._rhost, self._rport):
1327 _ThrowError("drbd%d: DRBD disk missing network info in"
1328 " DisconnectNet()", self.minor)
1330 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1331 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1332 sleep_time = 0.100 # we start the retry time at 100 miliseconds
1333 while time.time() < timeout_limit:
1334 status = self.GetProcStatus()
1335 if status.is_standalone:
1337 # retry the disconnect, it seems possible that due to a
1338 # well-time disconnect on the peer, my disconnect command might
1339 # be ingored and forgotten
1340 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1342 time.sleep(sleep_time)
1343 sleep_time = min(2, sleep_time * 1.5)
1345 if not status.is_standalone:
1346 if ever_disconnected:
1347 msg = ("drbd%d: device did not react to the"
1348 " 'disconnect' command in a timely manner")
1350 msg = "drbd%d: can't shutdown network, even after multiple retries"
1351 _ThrowError(msg, self.minor)
1353 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1354 if reconfig_time > 15: # hardcoded alert limit
1355 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1356 self.minor, reconfig_time)
1358 def AttachNet(self, multimaster):
1359 """Reconnects the network.
1361 This method connects the network side of the device with a
1362 specified multi-master flag. The device needs to be 'Standalone'
1363 but have valid network configuration data.
1366 - multimaster: init the network in dual-primary mode
1369 if self.minor is None:
1370 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1372 if None in (self._lhost, self._lport, self._rhost, self._rport):
1373 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1375 status = self.GetProcStatus()
1377 if not status.is_standalone:
1378 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1380 self._AssembleNet(self.minor,
1381 (self._lhost, self._lport, self._rhost, self._rport),
1382 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1383 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1386 """Check if our minor is configured.
1388 This doesn't do any device configurations - it only checks if the
1389 minor is in a state different from Unconfigured.
1391 Note that this function will not change the state of the system in
1392 any way (except in case of side-effects caused by reading from
1396 used_devs = self.GetUsedDevs()
1397 if self._aminor in used_devs:
1398 minor = self._aminor
1402 self._SetFromMinor(minor)
1403 return minor is not None
1406 """Assemble the drbd.
1409 - if we have a configured device, we try to ensure that it matches
1411 - if not, we create it from zero
1414 super(DRBD8, self).Assemble()
1417 if self.minor is None:
1418 # local device completely unconfigured
1419 self._FastAssemble()
1421 # we have to recheck the local and network status and try to fix
1423 self._SlowAssemble()
1425 def _SlowAssemble(self):
1426 """Assembles the DRBD device from a (partially) configured device.
1428 In case of partially attached (local device matches but no network
1429 setup), we perform the network attach. If successful, we re-test
1430 the attach if can return success.
1433 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1434 for minor in (self._aminor,):
1435 info = self._GetDevInfo(self._GetShowData(minor))
1436 match_l = self._MatchesLocal(info)
1437 match_r = self._MatchesNet(info)
1439 if match_l and match_r:
1440 # everything matches
1443 if match_l and not match_r and "local_addr" not in info:
1444 # disk matches, but not attached to network, attach and recheck
1445 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1446 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1447 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1450 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1451 " show' disagrees", minor)
1453 if match_r and "local_dev" not in info:
1454 # no local disk, but network attached and it matches
1455 self._AssembleLocal(minor, self._children[0].dev_path,
1456 self._children[1].dev_path, self.size)
1457 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1460 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1461 " show' disagrees", minor)
1463 # this case must be considered only if we actually have local
1464 # storage, i.e. not in diskless mode, because all diskless
1465 # devices are equal from the point of view of local
1467 if (match_l and "local_dev" in info and
1468 not match_r and "local_addr" in info):
1469 # strange case - the device network part points to somewhere
1470 # else, even though its local storage is ours; as we own the
1471 # drbd space, we try to disconnect from the remote peer and
1472 # reconnect to our correct one
1474 self._ShutdownNet(minor)
1475 except errors.BlockDeviceError, err:
1476 _ThrowError("drbd%d: device has correct local storage, wrong"
1477 " remote peer and is unable to disconnect in order"
1478 " to attach to the correct peer: %s", minor, str(err))
1479 # note: _AssembleNet also handles the case when we don't want
1480 # local storage (i.e. one or more of the _[lr](host|port) is
1482 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1483 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1484 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1487 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1488 " show' disagrees", minor)
1493 self._SetFromMinor(minor)
1495 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1498 def _FastAssemble(self):
1499 """Assemble the drbd device from zero.
1501 This is run when in Assemble we detect our minor is unused.
1504 minor = self._aminor
1505 if self._children and self._children[0] and self._children[1]:
1506 self._AssembleLocal(minor, self._children[0].dev_path,
1507 self._children[1].dev_path, self.size)
1508 if self._lhost and self._lport and self._rhost and self._rport:
1509 self._AssembleNet(minor,
1510 (self._lhost, self._lport, self._rhost, self._rport),
1511 constants.DRBD_NET_PROTOCOL,
1512 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1513 self._SetFromMinor(minor)
1516 def _ShutdownLocal(cls, minor):
1517 """Detach from the local device.
1519 I/Os will continue to be served from the remote device. If we
1520 don't have a remote device, this operation will fail.
1523 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1525 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1528 def _ShutdownNet(cls, minor):
1529 """Disconnect from the remote peer.
1531 This fails if we don't have a local device.
1534 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1536 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1539 def _ShutdownAll(cls, minor):
1540 """Deactivate the device.
1542 This will, of course, fail if the device is in use.
1545 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1547 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1548 minor, result.output)
1551 """Shutdown the DRBD device.
1554 if self.minor is None and not self.Attach():
1555 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1559 self.dev_path = None
1560 self._ShutdownAll(minor)
1563 """Stub remove for DRBD devices.
1569 def Create(cls, unique_id, children, size):
1570 """Create a new DRBD8 device.
1572 Since DRBD devices are not created per se, just assembled, this
1573 function only initializes the metadata.
1576 if len(children) != 2:
1577 raise errors.ProgrammerError("Invalid setup for the drbd device")
1578 # check that the minor is unused
1579 aminor = unique_id[4]
1580 proc_info = cls._MassageProcData(cls._GetProcData())
1581 if aminor in proc_info:
1582 status = DRBD8Status(proc_info[aminor])
1583 in_use = status.is_in_use
1587 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1590 if not meta.Attach():
1591 _ThrowError("drbd%d: can't attach to meta device '%s'",
1593 cls._CheckMetaSize(meta.dev_path)
1594 cls._InitMeta(aminor, meta.dev_path)
1595 return cls(unique_id, children, size)
1597 def Grow(self, amount):
1598 """Resize the DRBD device and its backing storage.
1601 if self.minor is None:
1602 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1603 if len(self._children) != 2 or None in self._children:
1604 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1605 self._children[0].Grow(amount)
1606 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1608 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1611 class FileStorage(BlockDev):
1614 This class represents the a file storage backend device.
1616 The unique_id for the file device is a (file_driver, file_path) tuple.
1619 def __init__(self, unique_id, children, size):
1620 """Initalizes a file device backend.
1624 raise errors.BlockDeviceError("Invalid setup for file device")
1625 super(FileStorage, self).__init__(unique_id, children, size)
1626 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1627 raise ValueError("Invalid configuration data %s" % str(unique_id))
1628 self.driver = unique_id[0]
1629 self.dev_path = unique_id[1]
1633 """Assemble the device.
1635 Checks whether the file device exists, raises BlockDeviceError otherwise.
1638 if not os.path.exists(self.dev_path):
1639 _ThrowError("File device '%s' does not exist" % self.dev_path)
1642 """Shutdown the device.
1644 This is a no-op for the file type, as we don't deacivate
1645 the file on shutdown.
1650 def Open(self, force=False):
1651 """Make the device ready for I/O.
1653 This is a no-op for the file type.
1659 """Notifies that the device will no longer be used for I/O.
1661 This is a no-op for the file type.
1667 """Remove the file backing the block device.
1670 @return: True if the removal was successful
1674 os.remove(self.dev_path)
1675 except OSError, err:
1676 if err.errno != errno.ENOENT:
1677 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1680 """Attach to an existing file.
1682 Check if this file already exists.
1685 @return: True if file exists
1688 self.attached = os.path.exists(self.dev_path)
1689 return self.attached
1692 def Create(cls, unique_id, children, size):
1693 """Create a new file.
1695 @param size: the size of file in MiB
1697 @rtype: L{bdev.FileStorage}
1698 @return: an instance of FileStorage
1701 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1702 raise ValueError("Invalid configuration data %s" % str(unique_id))
1703 dev_path = unique_id[1]
1704 if os.path.exists(dev_path):
1705 _ThrowError("File already existing: %s", dev_path)
1707 f = open(dev_path, 'w')
1708 f.truncate(size * 1024 * 1024)
1710 except IOError, err:
1711 _ThrowError("Error in file creation: %", str(err))
1713 return FileStorage(unique_id, children, size)
1717 constants.LD_LV: LogicalVolume,
1718 constants.LD_DRBD8: DRBD8,
1719 constants.LD_FILE: FileStorage,
1723 def FindDevice(dev_type, unique_id, children, size):
1724 """Search for an existing, assembled device.
1726 This will succeed only if the device exists and is assembled, but it
1727 does not do any actions in order to activate the device.
1730 if dev_type not in DEV_MAP:
1731 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1732 device = DEV_MAP[dev_type](unique_id, children, size)
1733 if not device.attached:
1738 def Assemble(dev_type, unique_id, children, size):
1739 """Try to attach or assemble an existing device.
1741 This will attach to assemble the device, as needed, to bring it
1742 fully up. It must be safe to run on already-assembled devices.
1745 if dev_type not in DEV_MAP:
1746 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1747 device = DEV_MAP[dev_type](unique_id, children, size)
1752 def Create(dev_type, unique_id, children, size):
1756 if dev_type not in DEV_MAP:
1757 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1758 device = DEV_MAP[dev_type].Create(unique_id, children, size)