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 ])
323 # The size constraint should have been checked from the master before
324 # calling the create function.
326 _ThrowError("Not enough free space: required %s,"
327 " available %s", size, free_size)
328 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-n%s" % lv_name,
331 _ThrowError("LV create failed (%s): %s",
332 result.fail_reason, result.output)
333 return LogicalVolume(unique_id, children, size)
336 def GetPVInfo(vg_name):
337 """Get the free space info for PVs in a volume group.
339 @param vg_name: the volume group name
342 @return: list of tuples (free_space, name) with free_space in mebibytes
345 command = ["pvs", "--noheadings", "--nosuffix", "--units=m",
346 "-opv_name,vg_name,pv_free,pv_attr", "--unbuffered",
348 result = utils.RunCmd(command)
350 logging.error("Can't get the PV information: %s - %s",
351 result.fail_reason, result.output)
354 for line in result.stdout.splitlines():
355 fields = line.strip().split(':')
357 logging.error("Can't parse pvs output: line '%s'", line)
359 # skip over pvs from another vg or ones which are not allocatable
360 if fields[1] != vg_name or fields[3][0] != 'a':
362 data.append((float(fields[2]), fields[0]))
367 """Remove this logical volume.
370 if not self.minor and not self.Attach():
371 # the LV does not exist
373 result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
374 (self._vg_name, self._lv_name)])
376 _ThrowError("Can't lvremove: %s - %s", result.fail_reason, result.output)
378 def Rename(self, new_id):
379 """Rename this logical volume.
382 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
383 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
384 new_vg, new_name = new_id
385 if new_vg != self._vg_name:
386 raise errors.ProgrammerError("Can't move a logical volume across"
387 " volume groups (from %s to to %s)" %
388 (self._vg_name, new_vg))
389 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
391 _ThrowError("Failed to rename the logical volume: %s", result.output)
392 self._lv_name = new_name
393 self.dev_path = "/dev/%s/%s" % (self._vg_name, self._lv_name)
396 """Attach to an existing LV.
398 This method will try to see if an existing and active LV exists
399 which matches our name. If so, its major/minor will be
403 self.attached = False
404 result = utils.RunCmd(["lvs", "--noheadings", "--separator=,",
405 "-olv_attr,lv_kernel_major,lv_kernel_minor",
408 logging.error("Can't find LV %s: %s, %s",
409 self.dev_path, result.fail_reason, result.output)
411 out = result.stdout.strip().rstrip(',')
414 logging.error("Can't parse LVS output, len(%s) != 3", str(out))
417 status, major, minor = out[:3]
419 logging.error("lvs lv_attr is not 6 characters (%s)", status)
425 except ValueError, err:
426 logging.error("lvs major/minor cannot be parsed: %s", str(err))
430 self._degraded = status[0] == 'v' # virtual volume, i.e. doesn't backing
436 """Assemble the device.
438 We alway run `lvchange -ay` on the LV to ensure it's active before
439 use, as there were cases when xenvg was not active after boot
440 (also possibly after disk issues).
443 result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
445 _ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
448 """Shutdown the device.
450 This is a no-op for the LV device type, as we don't deactivate the
456 def GetSyncStatus(self):
457 """Returns the sync status of the device.
459 If this device is a mirroring device, this function returns the
460 status of the mirror.
462 For logical volumes, sync_percent and estimated_time are always
463 None (no recovery in progress, as we don't handle the mirrored LV
464 case). The is_degraded parameter is the inverse of the ldisk
467 For the ldisk parameter, we check if the logical volume has the
468 'virtual' type, which means it's not backed by existing storage
469 anymore (read from it return I/O error). This happens after a
470 physical disk failure and subsequent 'vgreduce --removemissing' on
473 The status was already read in Attach, so we just return it.
476 @return: (sync_percent, estimated_time, is_degraded, ldisk)
479 return None, None, self._degraded, self._degraded
481 def Open(self, force=False):
482 """Make the device ready for I/O.
484 This is a no-op for the LV device type.
490 """Notifies that the device will no longer be used for I/O.
492 This is a no-op for the LV device type.
497 def Snapshot(self, size):
498 """Create a snapshot copy of an lvm block device.
501 snap_name = self._lv_name + ".snap"
503 # remove existing snapshot if found
504 snap = LogicalVolume((self._vg_name, snap_name), None, size)
505 _IgnoreError(snap.Remove)
507 pvs_info = self.GetPVInfo(self._vg_name)
509 _ThrowError("Can't compute PV info for vg %s", self._vg_name)
512 free_size, pv_name = pvs_info[0]
514 _ThrowError("Not enough free space: required %s,"
515 " available %s", size, free_size)
517 result = utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
518 "-n%s" % snap_name, self.dev_path])
520 _ThrowError("command: %s error: %s - %s",
521 result.cmd, result.fail_reason, result.output)
525 def SetInfo(self, text):
526 """Update metadata with info text.
529 BlockDev.SetInfo(self, text)
531 # Replace invalid characters
532 text = re.sub('^[^A-Za-z0-9_+.]', '_', text)
533 text = re.sub('[^-A-Za-z0-9_+.]', '_', text)
535 # Only up to 128 characters are allowed
538 result = utils.RunCmd(["lvchange", "--addtag", text,
541 _ThrowError("Command: %s error: %s - %s", result.cmd, result.fail_reason,
544 def Grow(self, amount):
545 """Grow the logical volume.
548 # we try multiple algorithms since the 'best' ones might not have
549 # space available in the right place, but later ones might (since
550 # they have less constraints); also note that only recent LVM
552 for alloc_policy in "contiguous", "cling", "normal":
553 result = utils.RunCmd(["lvextend", "--alloc", alloc_policy,
554 "-L", "+%dm" % amount, self.dev_path])
555 if not result.failed:
557 _ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
560 class DRBD8Status(object):
561 """A DRBD status representation class.
563 Note that this doesn't support unconfigured devices (cs:Unconfigured).
566 UNCONF_RE = re.compile(r"\s*[0-9]+:\s*cs:Unconfigured$")
567 LINE_RE = re.compile(r"\s*[0-9]+:\s*cs:(\S+)\s+(?:st|ro):([^/]+)/(\S+)"
568 "\s+ds:([^/]+)/(\S+)\s+.*$")
569 SYNC_RE = re.compile(r"^.*\ssync'ed:\s*([0-9.]+)%.*"
570 "\sfinish: ([0-9]+):([0-9]+):([0-9]+)\s.*$")
572 CS_UNCONFIGURED = "Unconfigured"
573 CS_STANDALONE = "StandAlone"
574 CS_WFCONNECTION = "WFConnection"
575 CS_WFREPORTPARAMS = "WFReportParams"
576 CS_CONNECTED = "Connected"
577 CS_STARTINGSYNCS = "StartingSyncS"
578 CS_STARTINGSYNCT = "StartingSyncT"
579 CS_WFBITMAPS = "WFBitMapS"
580 CS_WFBITMAPT = "WFBitMapT"
581 CS_WFSYNCUUID = "WFSyncUUID"
582 CS_SYNCSOURCE = "SyncSource"
583 CS_SYNCTARGET = "SyncTarget"
584 CS_PAUSEDSYNCS = "PausedSyncS"
585 CS_PAUSEDSYNCT = "PausedSyncT"
586 CSET_SYNC = frozenset([
599 DS_DISKLESS = "Diskless"
600 DS_ATTACHING = "Attaching" # transient state
601 DS_FAILED = "Failed" # transient state, next: diskless
602 DS_NEGOTIATING = "Negotiating" # transient state
603 DS_INCONSISTENT = "Inconsistent" # while syncing or after creation
604 DS_OUTDATED = "Outdated"
605 DS_DUNKNOWN = "DUnknown" # shown for peer disk when not connected
606 DS_CONSISTENT = "Consistent"
607 DS_UPTODATE = "UpToDate" # normal state
609 RO_PRIMARY = "Primary"
610 RO_SECONDARY = "Secondary"
611 RO_UNKNOWN = "Unknown"
613 def __init__(self, procline):
614 u = self.UNCONF_RE.match(procline)
616 self.cstatus = self.CS_UNCONFIGURED
617 self.lrole = self.rrole = self.ldisk = self.rdisk = None
619 m = self.LINE_RE.match(procline)
621 raise errors.BlockDeviceError("Can't parse input data '%s'" % procline)
622 self.cstatus = m.group(1)
623 self.lrole = m.group(2)
624 self.rrole = m.group(3)
625 self.ldisk = m.group(4)
626 self.rdisk = m.group(5)
628 # end reading of data from the LINE_RE or UNCONF_RE
630 self.is_standalone = self.cstatus == self.CS_STANDALONE
631 self.is_wfconn = self.cstatus == self.CS_WFCONNECTION
632 self.is_connected = self.cstatus == self.CS_CONNECTED
633 self.is_primary = self.lrole == self.RO_PRIMARY
634 self.is_secondary = self.lrole == self.RO_SECONDARY
635 self.peer_primary = self.rrole == self.RO_PRIMARY
636 self.peer_secondary = self.rrole == self.RO_SECONDARY
637 self.both_primary = self.is_primary and self.peer_primary
638 self.both_secondary = self.is_secondary and self.peer_secondary
640 self.is_diskless = self.ldisk == self.DS_DISKLESS
641 self.is_disk_uptodate = self.ldisk == self.DS_UPTODATE
643 self.is_in_resync = self.cstatus in self.CSET_SYNC
644 self.is_in_use = self.cstatus != self.CS_UNCONFIGURED
646 m = self.SYNC_RE.match(procline)
648 self.sync_percent = float(m.group(1))
649 hours = int(m.group(2))
650 minutes = int(m.group(3))
651 seconds = int(m.group(4))
652 self.est_time = hours * 3600 + minutes * 60 + seconds
654 # we have (in this if branch) no percent information, but if
655 # we're resyncing we need to 'fake' a sync percent information,
656 # as this is how cmdlib determines if it makes sense to wait for
658 if self.is_in_resync:
659 self.sync_percent = 0
661 self.sync_percent = None
665 class BaseDRBD(BlockDev):
668 This class contains a few bits of common functionality between the
669 0.7 and 8.x versions of DRBD.
672 _VERSION_RE = re.compile(r"^version: (\d+)\.(\d+)\.(\d+)"
673 r" \(api:(\d+)/proto:(\d+)(?:-(\d+))?\)")
676 _ST_UNCONFIGURED = "Unconfigured"
677 _ST_WFCONNECTION = "WFConnection"
678 _ST_CONNECTED = "Connected"
680 _STATUS_FILE = "/proc/drbd"
683 def _GetProcData(filename=_STATUS_FILE):
684 """Return data from /proc/drbd.
688 stat = open(filename, "r")
690 data = stat.read().splitlines()
693 except EnvironmentError, err:
694 if err.errno == errno.ENOENT:
695 _ThrowError("The file %s cannot be opened, check if the module"
696 " is loaded (%s)", filename, str(err))
698 _ThrowError("Can't read the DRBD proc file %s: %s", filename, str(err))
700 _ThrowError("Can't read any data from %s", filename)
704 def _MassageProcData(data):
705 """Transform the output of _GetProdData into a nicer form.
707 @return: a dictionary of minor: joined lines from /proc/drbd
711 lmatch = re.compile("^ *([0-9]+):.*$")
713 old_minor = old_line = None
715 lresult = lmatch.match(line)
716 if lresult is not None:
717 if old_minor is not None:
718 results[old_minor] = old_line
719 old_minor = int(lresult.group(1))
722 if old_minor is not None:
723 old_line += " " + line.strip()
725 if old_minor is not None:
726 results[old_minor] = old_line
730 def _GetVersion(cls):
731 """Return the DRBD version.
733 This will return a dict with keys:
739 - proto2 (only on drbd > 8.2.X)
742 proc_data = cls._GetProcData()
743 first_line = proc_data[0].strip()
744 version = cls._VERSION_RE.match(first_line)
746 raise errors.BlockDeviceError("Can't parse DRBD version from '%s'" %
749 values = version.groups()
750 retval = {'k_major': int(values[0]),
751 'k_minor': int(values[1]),
752 'k_point': int(values[2]),
753 'api': int(values[3]),
754 'proto': int(values[4]),
756 if values[5] is not None:
757 retval['proto2'] = values[5]
763 """Return the path to a drbd device for a given minor.
766 return "/dev/drbd%d" % minor
769 def GetUsedDevs(cls):
770 """Compute the list of used DRBD devices.
773 data = cls._GetProcData()
776 valid_line = re.compile("^ *([0-9]+): cs:([^ ]+).*$")
778 match = valid_line.match(line)
781 minor = int(match.group(1))
782 state = match.group(2)
783 if state == cls._ST_UNCONFIGURED:
785 used_devs[minor] = state, line
789 def _SetFromMinor(self, minor):
790 """Set our parameters based on the given minor.
792 This sets our minor variable and our dev_path.
796 self.minor = self.dev_path = None
797 self.attached = False
800 self.dev_path = self._DevPath(minor)
804 def _CheckMetaSize(meta_device):
805 """Check if the given meta device looks like a valid one.
807 This currently only check the size, which must be around
811 result = utils.RunCmd(["blockdev", "--getsize", meta_device])
813 _ThrowError("Failed to get device size: %s - %s",
814 result.fail_reason, result.output)
816 sectors = int(result.stdout)
818 _ThrowError("Invalid output from blockdev: '%s'", result.stdout)
819 bytes = sectors * 512
820 if bytes < 128 * 1024 * 1024: # less than 128MiB
821 _ThrowError("Meta device too small (%.2fMib)", (bytes / 1024 / 1024))
822 if bytes > (128 + 32) * 1024 * 1024: # account for an extra (big) PE on LVM
823 _ThrowError("Meta device too big (%.2fMiB)", (bytes / 1024 / 1024))
825 def Rename(self, new_id):
828 This is not supported for drbd devices.
831 raise errors.ProgrammerError("Can't rename a drbd device")
834 class DRBD8(BaseDRBD):
835 """DRBD v8.x block device.
837 This implements the local host part of the DRBD device, i.e. it
838 doesn't do anything to the supposed peer. If you need a fully
839 connected DRBD pair, you need to use this class on both hosts.
841 The unique_id for the drbd device is the (local_ip, local_port,
842 remote_ip, remote_port) tuple, and it must have two children: the
843 data device and the meta_device. The meta device is checked for
844 valid size and is zeroed on create.
851 _NET_RECONFIG_TIMEOUT = 60
853 def __init__(self, unique_id, children, size):
854 if children and children.count(None) > 0:
856 super(DRBD8, self).__init__(unique_id, children, size)
857 self.major = self._DRBD_MAJOR
858 version = self._GetVersion()
859 if version['k_major'] != 8 :
860 _ThrowError("Mismatch in DRBD kernel version and requested ganeti"
861 " usage: kernel is %s.%s, ganeti wants 8.x",
862 version['k_major'], version['k_minor'])
864 if len(children) not in (0, 2):
865 raise ValueError("Invalid configuration data %s" % str(children))
866 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 6:
867 raise ValueError("Invalid configuration data %s" % str(unique_id))
868 (self._lhost, self._lport,
869 self._rhost, self._rport,
870 self._aminor, self._secret) = unique_id
871 if (self._lhost is not None and self._lhost == self._rhost and
872 self._lport == self._rport):
873 raise ValueError("Invalid configuration data, same local/remote %s" %
878 def _InitMeta(cls, minor, dev_path):
879 """Initialize a meta device.
881 This will not work if the given minor is in use.
884 result = utils.RunCmd(["drbdmeta", "--force", cls._DevPath(minor),
885 "v08", dev_path, "0", "create-md"])
887 _ThrowError("Can't initialize meta device: %s", result.output)
890 def _FindUnusedMinor(cls):
891 """Find an unused DRBD device.
893 This is specific to 8.x as the minors are allocated dynamically,
894 so non-existing numbers up to a max minor count are actually free.
897 data = cls._GetProcData()
899 unused_line = re.compile("^ *([0-9]+): cs:Unconfigured$")
900 used_line = re.compile("^ *([0-9]+): cs:")
903 match = unused_line.match(line)
905 return int(match.group(1))
906 match = used_line.match(line)
908 minor = int(match.group(1))
909 highest = max(highest, minor)
910 if highest is None: # there are no minors in use at all
912 if highest >= cls._MAX_MINORS:
913 logging.error("Error: no free drbd minors!")
914 raise errors.BlockDeviceError("Can't find a free DRBD minor")
918 def _GetShowParser(cls):
919 """Return a parser for `drbd show` output.
921 This will either create or return an already-create parser for the
922 output of the command `drbd show`.
925 if cls._PARSE_SHOW is not None:
926 return cls._PARSE_SHOW
929 lbrace = pyp.Literal("{").suppress()
930 rbrace = pyp.Literal("}").suppress()
931 semi = pyp.Literal(";").suppress()
932 # this also converts the value to an int
933 number = pyp.Word(pyp.nums).setParseAction(lambda s, l, t: int(t[0]))
935 comment = pyp.Literal ("#") + pyp.Optional(pyp.restOfLine)
936 defa = pyp.Literal("_is_default").suppress()
937 dbl_quote = pyp.Literal('"').suppress()
939 keyword = pyp.Word(pyp.alphanums + '-')
942 value = pyp.Word(pyp.alphanums + '_-/.:')
943 quoted = dbl_quote + pyp.CharsNotIn('"') + dbl_quote
944 addr_type = (pyp.Optional(pyp.Literal("ipv4")).suppress() +
945 pyp.Optional(pyp.Literal("ipv6")).suppress())
946 addr_port = (addr_type + pyp.Word(pyp.nums + '.') +
947 pyp.Literal(':').suppress() + number)
948 # meta device, extended syntax
949 meta_value = ((value ^ quoted) + pyp.Literal('[').suppress() +
950 number + pyp.Word(']').suppress())
951 # device name, extended syntax
952 device_value = pyp.Literal("minor").suppress() + number
955 stmt = (~rbrace + keyword + ~lbrace +
956 pyp.Optional(addr_port ^ value ^ quoted ^ meta_value ^
958 pyp.Optional(defa) + semi +
959 pyp.Optional(pyp.restOfLine).suppress())
962 section_name = pyp.Word(pyp.alphas + '_')
963 section = section_name + lbrace + pyp.ZeroOrMore(pyp.Group(stmt)) + rbrace
965 bnf = pyp.ZeroOrMore(pyp.Group(section ^ stmt))
968 cls._PARSE_SHOW = bnf
973 def _GetShowData(cls, minor):
974 """Return the `drbdsetup show` data for a minor.
977 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "show"])
979 logging.error("Can't display the drbd config: %s - %s",
980 result.fail_reason, result.output)
985 def _GetDevInfo(cls, out):
986 """Parse details about a given DRBD minor.
988 This return, if available, the local backing device (as a path)
989 and the local and remote (ip, port) information from a string
990 containing the output of the `drbdsetup show` command as returned
998 bnf = cls._GetShowParser()
1002 results = bnf.parseString(out)
1003 except pyp.ParseException, err:
1004 _ThrowError("Can't parse drbdsetup show output: %s", str(err))
1006 # and massage the results into our desired format
1007 for section in results:
1009 if sname == "_this_host":
1010 for lst in section[1:]:
1011 if lst[0] == "disk":
1012 data["local_dev"] = lst[1]
1013 elif lst[0] == "meta-disk":
1014 data["meta_dev"] = lst[1]
1015 data["meta_index"] = lst[2]
1016 elif lst[0] == "address":
1017 data["local_addr"] = tuple(lst[1:])
1018 elif sname == "_remote_host":
1019 for lst in section[1:]:
1020 if lst[0] == "address":
1021 data["remote_addr"] = tuple(lst[1:])
1024 def _MatchesLocal(self, info):
1025 """Test if our local config matches with an existing device.
1027 The parameter should be as returned from `_GetDevInfo()`. This
1028 method tests if our local backing device is the same as the one in
1029 the info parameter, in effect testing if we look like the given
1034 backend, meta = self._children
1036 backend = meta = None
1038 if backend is not None:
1039 retval = ("local_dev" in info and info["local_dev"] == backend.dev_path)
1041 retval = ("local_dev" not in info)
1043 if meta is not None:
1044 retval = retval and ("meta_dev" in info and
1045 info["meta_dev"] == meta.dev_path)
1046 retval = retval and ("meta_index" in info and
1047 info["meta_index"] == 0)
1049 retval = retval and ("meta_dev" not in info and
1050 "meta_index" not in info)
1053 def _MatchesNet(self, info):
1054 """Test if our network config matches with an existing device.
1056 The parameter should be as returned from `_GetDevInfo()`. This
1057 method tests if our network configuration is the same as the one
1058 in the info parameter, in effect testing if we look like the given
1062 if (((self._lhost is None and not ("local_addr" in info)) and
1063 (self._rhost is None and not ("remote_addr" in info)))):
1066 if self._lhost is None:
1069 if not ("local_addr" in info and
1070 "remote_addr" in info):
1073 retval = (info["local_addr"] == (self._lhost, self._lport))
1074 retval = (retval and
1075 info["remote_addr"] == (self._rhost, self._rport))
1079 def _AssembleLocal(cls, minor, backend, meta, size):
1080 """Configure the local part of a DRBD device.
1083 args = ["drbdsetup", cls._DevPath(minor), "disk",
1088 result = utils.RunCmd(args)
1090 _ThrowError("drbd%d: can't attach local disk: %s", minor, result.output)
1093 def _AssembleNet(cls, minor, net_info, protocol,
1094 dual_pri=False, hmac=None, secret=None):
1095 """Configure the network part of the device.
1098 lhost, lport, rhost, rport = net_info
1099 if None in net_info:
1100 # we don't want network connection and actually want to make
1102 cls._ShutdownNet(minor)
1105 # Workaround for a race condition. When DRBD is doing its dance to
1106 # establish a connection with its peer, it also sends the
1107 # synchronization speed over the wire. In some cases setting the
1108 # sync speed only after setting up both sides can race with DRBD
1109 # connecting, hence we set it here before telling DRBD anything
1111 cls._SetMinorSyncSpeed(minor, constants.SYNC_SPEED)
1113 args = ["drbdsetup", cls._DevPath(minor), "net",
1114 "%s:%s" % (lhost, lport), "%s:%s" % (rhost, rport), protocol,
1115 "-A", "discard-zero-changes",
1122 args.extend(["-a", hmac, "-x", secret])
1123 result = utils.RunCmd(args)
1125 _ThrowError("drbd%d: can't setup network: %s - %s",
1126 minor, result.fail_reason, result.output)
1128 timeout = time.time() + 10
1130 while time.time() < timeout:
1131 info = cls._GetDevInfo(cls._GetShowData(minor))
1132 if not "local_addr" in info or not "remote_addr" in info:
1135 if (info["local_addr"] != (lhost, lport) or
1136 info["remote_addr"] != (rhost, rport)):
1142 _ThrowError("drbd%d: timeout while configuring network", minor)
1144 def AddChildren(self, devices):
1145 """Add a disk to the DRBD device.
1148 if self.minor is None:
1149 _ThrowError("drbd%d: can't attach to dbrd8 during AddChildren",
1151 if len(devices) != 2:
1152 _ThrowError("drbd%d: need two devices for AddChildren", self.minor)
1153 info = self._GetDevInfo(self._GetShowData(self.minor))
1154 if "local_dev" in info:
1155 _ThrowError("drbd%d: already attached to a local disk", self.minor)
1156 backend, meta = devices
1157 if backend.dev_path is None or meta.dev_path is None:
1158 _ThrowError("drbd%d: children not ready during AddChildren", self.minor)
1161 self._CheckMetaSize(meta.dev_path)
1162 self._InitMeta(self._FindUnusedMinor(), meta.dev_path)
1164 self._AssembleLocal(self.minor, backend.dev_path, meta.dev_path, self.size)
1165 self._children = devices
1167 def RemoveChildren(self, devices):
1168 """Detach the drbd device from local storage.
1171 if self.minor is None:
1172 _ThrowError("drbd%d: can't attach to drbd8 during RemoveChildren",
1174 # early return if we don't actually have backing storage
1175 info = self._GetDevInfo(self._GetShowData(self.minor))
1176 if "local_dev" not in info:
1178 if len(self._children) != 2:
1179 _ThrowError("drbd%d: we don't have two children: %s", self.minor,
1181 if self._children.count(None) == 2: # we don't actually have children :)
1182 logging.warning("drbd%d: requested detach while detached", self.minor)
1184 if len(devices) != 2:
1185 _ThrowError("drbd%d: we need two children in RemoveChildren", self.minor)
1186 for child, dev in zip(self._children, devices):
1187 if dev != child.dev_path:
1188 _ThrowError("drbd%d: mismatch in local storage (%s != %s) in"
1189 " RemoveChildren", self.minor, dev, child.dev_path)
1191 self._ShutdownLocal(self.minor)
1195 def _SetMinorSyncSpeed(cls, minor, kbytes):
1196 """Set the speed of the DRBD syncer.
1198 This is the low-level implementation.
1201 @param minor: the drbd minor whose settings we change
1203 @param kbytes: the speed in kbytes/second
1205 @return: the success of the operation
1208 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "syncer",
1209 "-r", "%d" % kbytes, "--create-device"])
1211 logging.error("Can't change syncer rate: %s - %s",
1212 result.fail_reason, result.output)
1213 return not result.failed
1215 def SetSyncSpeed(self, kbytes):
1216 """Set the speed of the DRBD syncer.
1219 @param kbytes: the speed in kbytes/second
1221 @return: the success of the operation
1224 if self.minor is None:
1225 logging.info("Not attached during SetSyncSpeed")
1227 children_result = super(DRBD8, self).SetSyncSpeed(kbytes)
1228 return self._SetMinorSyncSpeed(self.minor, kbytes) and children_result
1230 def GetProcStatus(self):
1231 """Return device data from /proc.
1234 if self.minor is None:
1235 _ThrowError("drbd%d: GetStats() called while not attached", self._aminor)
1236 proc_info = self._MassageProcData(self._GetProcData())
1237 if self.minor not in proc_info:
1238 _ThrowError("drbd%d: can't find myself in /proc", self.minor)
1239 return DRBD8Status(proc_info[self.minor])
1241 def GetSyncStatus(self):
1242 """Returns the sync status of the device.
1245 If sync_percent is None, it means all is ok
1246 If estimated_time is None, it means we can't esimate
1247 the time needed, otherwise it's the time left in seconds.
1250 We set the is_degraded parameter to True on two conditions:
1251 network not connected or local disk missing.
1253 We compute the ldisk parameter based on wheter we have a local
1257 @return: (sync_percent, estimated_time, is_degraded, ldisk)
1260 if self.minor is None and not self.Attach():
1261 _ThrowError("drbd%d: can't Attach() in GetSyncStatus", self._aminor)
1262 stats = self.GetProcStatus()
1263 ldisk = not stats.is_disk_uptodate
1264 is_degraded = not stats.is_connected
1265 return stats.sync_percent, stats.est_time, is_degraded or ldisk, ldisk
1267 def Open(self, force=False):
1268 """Make the local state primary.
1270 If the 'force' parameter is given, the '-o' option is passed to
1271 drbdsetup. Since this is a potentially dangerous operation, the
1272 force flag should be only given after creation, when it actually
1276 if self.minor is None and not self.Attach():
1277 logging.error("DRBD cannot attach to a device during open")
1279 cmd = ["drbdsetup", self.dev_path, "primary"]
1282 result = utils.RunCmd(cmd)
1284 _ThrowError("drbd%d: can't make drbd device primary: %s", self.minor,
1288 """Make the local state secondary.
1290 This will, of course, fail if the device is in use.
1293 if self.minor is None and not self.Attach():
1294 _ThrowError("drbd%d: can't Attach() in Close()", self._aminor)
1295 result = utils.RunCmd(["drbdsetup", self.dev_path, "secondary"])
1297 _ThrowError("drbd%d: can't switch drbd device to secondary: %s",
1298 self.minor, result.output)
1300 def DisconnectNet(self):
1301 """Removes network configuration.
1303 This method shutdowns the network side of the device.
1305 The method will wait up to a hardcoded timeout for the device to
1306 go into standalone after the 'disconnect' command before
1307 re-configuring it, as sometimes it takes a while for the
1308 disconnect to actually propagate and thus we might issue a 'net'
1309 command while the device is still connected. If the device will
1310 still be attached to the network and we time out, we raise an
1314 if self.minor is None:
1315 _ThrowError("drbd%d: disk not attached in re-attach net", self._aminor)
1317 if None in (self._lhost, self._lport, self._rhost, self._rport):
1318 _ThrowError("drbd%d: DRBD disk missing network info in"
1319 " DisconnectNet()", self.minor)
1321 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor)
1322 timeout_limit = time.time() + self._NET_RECONFIG_TIMEOUT
1323 sleep_time = 0.100 # we start the retry time at 100 miliseconds
1324 while time.time() < timeout_limit:
1325 status = self.GetProcStatus()
1326 if status.is_standalone:
1328 # retry the disconnect, it seems possible that due to a
1329 # well-time disconnect on the peer, my disconnect command might
1330 # be ingored and forgotten
1331 ever_disconnected = _IgnoreError(self._ShutdownNet, self.minor) or \
1333 time.sleep(sleep_time)
1334 sleep_time = min(2, sleep_time * 1.5)
1336 if not status.is_standalone:
1337 if ever_disconnected:
1338 msg = ("drbd%d: device did not react to the"
1339 " 'disconnect' command in a timely manner")
1341 msg = "drbd%d: can't shutdown network, even after multiple retries"
1342 _ThrowError(msg, self.minor)
1344 reconfig_time = time.time() - timeout_limit + self._NET_RECONFIG_TIMEOUT
1345 if reconfig_time > 15: # hardcoded alert limit
1346 logging.info("drbd%d: DisconnectNet: detach took %.3f seconds",
1347 self.minor, reconfig_time)
1349 def AttachNet(self, multimaster):
1350 """Reconnects the network.
1352 This method connects the network side of the device with a
1353 specified multi-master flag. The device needs to be 'Standalone'
1354 but have valid network configuration data.
1357 - multimaster: init the network in dual-primary mode
1360 if self.minor is None:
1361 _ThrowError("drbd%d: device not attached in AttachNet", self._aminor)
1363 if None in (self._lhost, self._lport, self._rhost, self._rport):
1364 _ThrowError("drbd%d: missing network info in AttachNet()", self.minor)
1366 status = self.GetProcStatus()
1368 if not status.is_standalone:
1369 _ThrowError("drbd%d: device is not standalone in AttachNet", self.minor)
1371 self._AssembleNet(self.minor,
1372 (self._lhost, self._lport, self._rhost, self._rport),
1373 constants.DRBD_NET_PROTOCOL, dual_pri=multimaster,
1374 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1377 """Check if our minor is configured.
1379 This doesn't do any device configurations - it only checks if the
1380 minor is in a state different from Unconfigured.
1382 Note that this function will not change the state of the system in
1383 any way (except in case of side-effects caused by reading from
1387 used_devs = self.GetUsedDevs()
1388 if self._aminor in used_devs:
1389 minor = self._aminor
1393 self._SetFromMinor(minor)
1394 return minor is not None
1397 """Assemble the drbd.
1400 - if we have a configured device, we try to ensure that it matches
1402 - if not, we create it from zero
1405 super(DRBD8, self).Assemble()
1408 if self.minor is None:
1409 # local device completely unconfigured
1410 self._FastAssemble()
1412 # we have to recheck the local and network status and try to fix
1414 self._SlowAssemble()
1416 def _SlowAssemble(self):
1417 """Assembles the DRBD device from a (partially) configured device.
1419 In case of partially attached (local device matches but no network
1420 setup), we perform the network attach. If successful, we re-test
1421 the attach if can return success.
1424 net_data = (self._lhost, self._lport, self._rhost, self._rport)
1425 for minor in (self._aminor,):
1426 info = self._GetDevInfo(self._GetShowData(minor))
1427 match_l = self._MatchesLocal(info)
1428 match_r = self._MatchesNet(info)
1430 if match_l and match_r:
1431 # everything matches
1434 if match_l and not match_r and "local_addr" not in info:
1435 # disk matches, but not attached to network, attach and recheck
1436 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1437 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1438 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1441 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1442 " show' disagrees", minor)
1444 if match_r and "local_dev" not in info:
1445 # no local disk, but network attached and it matches
1446 self._AssembleLocal(minor, self._children[0].dev_path,
1447 self._children[1].dev_path, self.size)
1448 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1451 _ThrowError("drbd%d: disk attach successful, but 'drbdsetup"
1452 " show' disagrees", minor)
1454 # this case must be considered only if we actually have local
1455 # storage, i.e. not in diskless mode, because all diskless
1456 # devices are equal from the point of view of local
1458 if (match_l and "local_dev" in info and
1459 not match_r and "local_addr" in info):
1460 # strange case - the device network part points to somewhere
1461 # else, even though its local storage is ours; as we own the
1462 # drbd space, we try to disconnect from the remote peer and
1463 # reconnect to our correct one
1465 self._ShutdownNet(minor)
1466 except errors.BlockDeviceError, err:
1467 _ThrowError("drbd%d: device has correct local storage, wrong"
1468 " remote peer and is unable to disconnect in order"
1469 " to attach to the correct peer: %s", minor, str(err))
1470 # note: _AssembleNet also handles the case when we don't want
1471 # local storage (i.e. one or more of the _[lr](host|port) is
1473 self._AssembleNet(minor, net_data, constants.DRBD_NET_PROTOCOL,
1474 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1475 if self._MatchesNet(self._GetDevInfo(self._GetShowData(minor))):
1478 _ThrowError("drbd%d: network attach successful, but 'drbdsetup"
1479 " show' disagrees", minor)
1484 self._SetFromMinor(minor)
1486 _ThrowError("drbd%d: cannot activate, unknown or unhandled reason",
1489 def _FastAssemble(self):
1490 """Assemble the drbd device from zero.
1492 This is run when in Assemble we detect our minor is unused.
1495 minor = self._aminor
1496 if self._children and self._children[0] and self._children[1]:
1497 self._AssembleLocal(minor, self._children[0].dev_path,
1498 self._children[1].dev_path, self.size)
1499 if self._lhost and self._lport and self._rhost and self._rport:
1500 self._AssembleNet(minor,
1501 (self._lhost, self._lport, self._rhost, self._rport),
1502 constants.DRBD_NET_PROTOCOL,
1503 hmac=constants.DRBD_HMAC_ALG, secret=self._secret)
1504 self._SetFromMinor(minor)
1507 def _ShutdownLocal(cls, minor):
1508 """Detach from the local device.
1510 I/Os will continue to be served from the remote device. If we
1511 don't have a remote device, this operation will fail.
1514 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "detach"])
1516 _ThrowError("drbd%d: can't detach local disk: %s", minor, result.output)
1519 def _ShutdownNet(cls, minor):
1520 """Disconnect from the remote peer.
1522 This fails if we don't have a local device.
1525 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "disconnect"])
1527 _ThrowError("drbd%d: can't shutdown network: %s", minor, result.output)
1530 def _ShutdownAll(cls, minor):
1531 """Deactivate the device.
1533 This will, of course, fail if the device is in use.
1536 result = utils.RunCmd(["drbdsetup", cls._DevPath(minor), "down"])
1538 _ThrowError("drbd%d: can't shutdown drbd device: %s",
1539 minor, result.output)
1542 """Shutdown the DRBD device.
1545 if self.minor is None and not self.Attach():
1546 logging.info("drbd%d: not attached during Shutdown()", self._aminor)
1550 self.dev_path = None
1551 self._ShutdownAll(minor)
1554 """Stub remove for DRBD devices.
1560 def Create(cls, unique_id, children, size):
1561 """Create a new DRBD8 device.
1563 Since DRBD devices are not created per se, just assembled, this
1564 function only initializes the metadata.
1567 if len(children) != 2:
1568 raise errors.ProgrammerError("Invalid setup for the drbd device")
1569 # check that the minor is unused
1570 aminor = unique_id[4]
1571 proc_info = cls._MassageProcData(cls._GetProcData())
1572 if aminor in proc_info:
1573 status = DRBD8Status(proc_info[aminor])
1574 in_use = status.is_in_use
1578 _ThrowError("drbd%d: minor is already in use at Create() time", aminor)
1581 if not meta.Attach():
1582 _ThrowError("drbd%d: can't attach to meta device '%s'",
1584 cls._CheckMetaSize(meta.dev_path)
1585 cls._InitMeta(aminor, meta.dev_path)
1586 return cls(unique_id, children, size)
1588 def Grow(self, amount):
1589 """Resize the DRBD device and its backing storage.
1592 if self.minor is None:
1593 _ThrowError("drbd%d: Grow called while not attached", self._aminor)
1594 if len(self._children) != 2 or None in self._children:
1595 _ThrowError("drbd%d: cannot grow diskless device", self.minor)
1596 self._children[0].Grow(amount)
1597 result = utils.RunCmd(["drbdsetup", self.dev_path, "resize"])
1599 _ThrowError("drbd%d: resize failed: %s", self.minor, result.output)
1602 class FileStorage(BlockDev):
1605 This class represents the a file storage backend device.
1607 The unique_id for the file device is a (file_driver, file_path) tuple.
1610 def __init__(self, unique_id, children, size):
1611 """Initalizes a file device backend.
1615 raise errors.BlockDeviceError("Invalid setup for file device")
1616 super(FileStorage, self).__init__(unique_id, children, size)
1617 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1618 raise ValueError("Invalid configuration data %s" % str(unique_id))
1619 self.driver = unique_id[0]
1620 self.dev_path = unique_id[1]
1624 """Assemble the device.
1626 Checks whether the file device exists, raises BlockDeviceError otherwise.
1629 if not os.path.exists(self.dev_path):
1630 _ThrowError("File device '%s' does not exist" % self.dev_path)
1633 """Shutdown the device.
1635 This is a no-op for the file type, as we don't deacivate
1636 the file on shutdown.
1641 def Open(self, force=False):
1642 """Make the device ready for I/O.
1644 This is a no-op for the file type.
1650 """Notifies that the device will no longer be used for I/O.
1652 This is a no-op for the file type.
1658 """Remove the file backing the block device.
1661 @return: True if the removal was successful
1665 os.remove(self.dev_path)
1666 except OSError, err:
1667 if err.errno != errno.ENOENT:
1668 _ThrowError("Can't remove file '%s': %s", self.dev_path, err)
1671 """Attach to an existing file.
1673 Check if this file already exists.
1676 @return: True if file exists
1679 self.attached = os.path.exists(self.dev_path)
1680 return self.attached
1683 def Create(cls, unique_id, children, size):
1684 """Create a new file.
1686 @param size: the size of file in MiB
1688 @rtype: L{bdev.FileStorage}
1689 @return: an instance of FileStorage
1692 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
1693 raise ValueError("Invalid configuration data %s" % str(unique_id))
1694 dev_path = unique_id[1]
1695 if os.path.exists(dev_path):
1696 _ThrowError("File already existing: %s", dev_path)
1698 f = open(dev_path, 'w')
1699 f.truncate(size * 1024 * 1024)
1701 except IOError, err:
1702 _ThrowError("Error in file creation: %", str(err))
1704 return FileStorage(unique_id, children, size)
1708 constants.LD_LV: LogicalVolume,
1709 constants.LD_DRBD8: DRBD8,
1710 constants.LD_FILE: FileStorage,
1714 def FindDevice(dev_type, unique_id, children, size):
1715 """Search for an existing, assembled device.
1717 This will succeed only if the device exists and is assembled, but it
1718 does not do any actions in order to activate the device.
1721 if dev_type not in DEV_MAP:
1722 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1723 device = DEV_MAP[dev_type](unique_id, children, size)
1724 if not device.attached:
1729 def Assemble(dev_type, unique_id, children, size):
1730 """Try to attach or assemble an existing device.
1732 This will attach to assemble the device, as needed, to bring it
1733 fully up. It must be safe to run on already-assembled devices.
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)
1743 def Create(dev_type, unique_id, children, size):
1747 if dev_type not in DEV_MAP:
1748 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1749 device = DEV_MAP[dev_type].Create(unique_id, children, size)