Package ganeti :: Package storage :: Module bdev
[hide private]
[frames] | no frames]

Source Code for Module ganeti.storage.bdev

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc. 
   5  # All rights reserved. 
   6  # 
   7  # Redistribution and use in source and binary forms, with or without 
   8  # modification, are permitted provided that the following conditions are 
   9  # met: 
  10  # 
  11  # 1. Redistributions of source code must retain the above copyright notice, 
  12  # this list of conditions and the following disclaimer. 
  13  # 
  14  # 2. Redistributions in binary form must reproduce the above copyright 
  15  # notice, this list of conditions and the following disclaimer in the 
  16  # documentation and/or other materials provided with the distribution. 
  17  # 
  18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
  19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
  20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  29   
  30   
  31  """Block device abstraction. 
  32   
  33  """ 
  34   
  35  import re 
  36  import stat 
  37  import os 
  38  import logging 
  39  import math 
  40   
  41  from ganeti import utils 
  42  from ganeti import errors 
  43  from ganeti import constants 
  44  from ganeti import objects 
  45  from ganeti import compat 
  46  from ganeti import serializer 
  47  from ganeti.storage import base 
  48  from ganeti.storage import drbd 
  49  from ganeti.storage.filestorage import FileStorage 
  50  from ganeti.storage.gluster import GlusterStorage 
  51  from ganeti.storage.extstorage import ExtStorageDevice 
52 53 54 -class RbdShowmappedJsonError(Exception):
55 """`rbd showmmapped' JSON formatting error Exception class. 56 57 """ 58 pass
59
60 61 -def _CheckResult(result):
62 """Throws an error if the given result is a failed one. 63 64 @param result: result from RunCmd 65 66 """ 67 if result.failed: 68 base.ThrowError("Command: %s error: %s - %s", 69 result.cmd, result.fail_reason, result.output)
70
71 72 -class LogicalVolume(base.BlockDev):
73 """Logical Volume block device. 74 75 """ 76 _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$") 77 _PARSE_PV_DEV_RE = re.compile(r"^([^ ()]+)\([0-9]+\)$") 78 _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"]) 79 _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"]) 80
81 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
82 """Attaches to a LV device. 83 84 The unique_id is a tuple (vg_name, lv_name) 85 86 """ 87 super(LogicalVolume, self).__init__(unique_id, children, size, params, 88 dyn_params, *args) 89 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 90 raise ValueError("Invalid configuration data %s" % str(unique_id)) 91 self._vg_name, self._lv_name = unique_id 92 self._ValidateName(self._vg_name) 93 self._ValidateName(self._lv_name) 94 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name) 95 self._degraded = True 96 self.major = self.minor = self.pe_size = self.stripe_count = None 97 self.pv_names = None 98 self.Attach()
99 100 @staticmethod
101 - def _GetStdPvSize(pvs_info):
102 """Return the the standard PV size (used with exclusive storage). 103 104 @param pvs_info: list of objects.LvmPvInfo, cannot be empty 105 @rtype: float 106 @return: size in MiB 107 108 """ 109 assert len(pvs_info) > 0 110 smallest = min([pv.size for pv in pvs_info]) 111 return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)
112 113 @staticmethod
114 - def _ComputeNumPvs(size, pvs_info):
115 """Compute the number of PVs needed for an LV (with exclusive storage). 116 117 @type size: float 118 @param size: LV size in MiB 119 @param pvs_info: list of objects.LvmPvInfo, cannot be empty 120 @rtype: integer 121 @return: number of PVs needed 122 """ 123 assert len(pvs_info) > 0 124 pv_size = float(LogicalVolume._GetStdPvSize(pvs_info)) 125 return int(math.ceil(float(size) / pv_size))
126 127 @staticmethod
128 - def _GetEmptyPvNames(pvs_info, max_pvs=None):
129 """Return a list of empty PVs, by name. 130 131 """ 132 empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info) 133 if max_pvs is not None: 134 empty_pvs = empty_pvs[:max_pvs] 135 return map((lambda pv: pv.name), empty_pvs)
136 137 @classmethod
138 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 139 dyn_params, *args):
140 """Create a new logical volume. 141 142 """ 143 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 144 raise errors.ProgrammerError("Invalid configuration data %s" % 145 str(unique_id)) 146 vg_name, lv_name = unique_id 147 cls._ValidateName(vg_name) 148 cls._ValidateName(lv_name) 149 pvs_info = cls.GetPVInfo([vg_name]) 150 if not pvs_info: 151 if excl_stor: 152 msg = "No (empty) PVs found" 153 else: 154 msg = "Can't compute PV info for vg %s" % vg_name 155 base.ThrowError(msg) 156 pvs_info.sort(key=(lambda pv: pv.free), reverse=True) 157 158 pvlist = [pv.name for pv in pvs_info] 159 if compat.any(":" in v for v in pvlist): 160 base.ThrowError("Some of your PVs have the invalid character ':' in their" 161 " name, this is not supported - please filter them out" 162 " in lvm.conf using either 'filter' or 'preferred_names'") 163 164 current_pvs = len(pvlist) 165 desired_stripes = params[constants.LDP_STRIPES] 166 stripes = min(current_pvs, desired_stripes) 167 168 if excl_stor: 169 if spindles is None: 170 base.ThrowError("Unspecified number of spindles: this is required" 171 "when exclusive storage is enabled, try running" 172 " gnt-cluster repair-disk-sizes") 173 (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info) 174 if err_msgs: 175 for m in err_msgs: 176 logging.warning(m) 177 req_pvs = cls._ComputeNumPvs(size, pvs_info) 178 if spindles < req_pvs: 179 base.ThrowError("Requested number of spindles (%s) is not enough for" 180 " a disk of %d MB (at least %d spindles needed)", 181 spindles, size, req_pvs) 182 else: 183 req_pvs = spindles 184 pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs) 185 current_pvs = len(pvlist) 186 if current_pvs < req_pvs: 187 base.ThrowError("Not enough empty PVs (spindles) to create a disk of %d" 188 " MB: %d available, %d needed", 189 size, current_pvs, req_pvs) 190 assert current_pvs == len(pvlist) 191 # We must update stripes to be sure to use all the desired spindles 192 stripes = current_pvs 193 if stripes > desired_stripes: 194 # Don't warn when lowering stripes, as it's no surprise 195 logging.warning("Using %s stripes instead of %s, to be able to use" 196 " %s spindles", stripes, desired_stripes, current_pvs) 197 198 else: 199 if stripes < desired_stripes: 200 logging.warning("Could not use %d stripes for VG %s, as only %d PVs are" 201 " available.", desired_stripes, vg_name, current_pvs) 202 free_size = sum([pv.free for pv in pvs_info]) 203 # The size constraint should have been checked from the master before 204 # calling the create function. 205 if free_size < size: 206 base.ThrowError("Not enough free space: required %s," 207 " available %s", size, free_size) 208 209 # If the free space is not well distributed, we won't be able to 210 # create an optimally-striped volume; in that case, we want to try 211 # with N, N-1, ..., 2, and finally 1 (non-stripped) number of 212 # stripes 213 cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name] 214 for stripes_arg in range(stripes, 0, -1): 215 result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist) 216 if not result.failed: 217 break 218 if result.failed: 219 base.ThrowError("LV create failed (%s): %s", 220 result.fail_reason, result.output) 221 return LogicalVolume(unique_id, children, size, params, dyn_params, *args)
222 223 @staticmethod
224 - def _GetVolumeInfo(lvm_cmd, fields):
225 """Returns LVM Volume infos using lvm_cmd 226 227 @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs" 228 @param fields: Fields to return 229 @return: A list of dicts each with the parsed fields 230 231 """ 232 if not fields: 233 raise errors.ProgrammerError("No fields specified") 234 235 sep = "|" 236 cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered", 237 "--separator=%s" % sep, "-o%s" % ",".join(fields)] 238 239 result = utils.RunCmd(cmd) 240 if result.failed: 241 raise errors.CommandError("Can't get the volume information: %s - %s" % 242 (result.fail_reason, result.output)) 243 244 data = [] 245 for line in result.stdout.splitlines(): 246 splitted_fields = line.strip().split(sep) 247 248 if len(fields) != len(splitted_fields): 249 raise errors.CommandError("Can't parse %s output: line '%s'" % 250 (lvm_cmd, line)) 251 252 data.append(splitted_fields) 253 254 return data
255 256 @classmethod
257 - def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
258 """Get the free space info for PVs in a volume group. 259 260 @param vg_names: list of volume group names, if empty all will be returned 261 @param filter_allocatable: whether to skip over unallocatable PVs 262 @param include_lvs: whether to include a list of LVs hosted on each PV 263 264 @rtype: list 265 @return: list of objects.LvmPvInfo objects 266 267 """ 268 # We request "lv_name" field only if we care about LVs, so we don't get 269 # a long list of entries with many duplicates unless we really have to. 270 # The duplicate "pv_name" field will be ignored. 271 if include_lvs: 272 lvfield = "lv_name" 273 else: 274 lvfield = "pv_name" 275 try: 276 info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free", 277 "pv_attr", "pv_size", lvfield]) 278 except errors.GenericError, err: 279 logging.error("Can't get PV information: %s", err) 280 return None 281 282 # When asked for LVs, "pvs" may return multiple entries for the same PV-LV 283 # pair. We sort entries by PV name and then LV name, so it's easy to weed 284 # out duplicates. 285 if include_lvs: 286 info.sort(key=(lambda i: (i[0], i[5]))) 287 data = [] 288 lastpvi = None 289 for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info: 290 # (possibly) skip over pvs which are not allocatable 291 if filter_allocatable and pv_attr[0] != "a": 292 continue 293 # (possibly) skip over pvs which are not in the right volume group(s) 294 if vg_names and vg_name not in vg_names: 295 continue 296 # Beware of duplicates (check before inserting) 297 if lastpvi and lastpvi.name == pv_name: 298 if include_lvs and lv_name: 299 if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name: 300 lastpvi.lv_list.append(lv_name) 301 else: 302 if include_lvs and lv_name: 303 lvl = [lv_name] 304 else: 305 lvl = [] 306 lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name, 307 size=float(pv_size), free=float(pv_free), 308 attributes=pv_attr, lv_list=lvl) 309 data.append(lastpvi) 310 311 return data
312 313 @classmethod
314 - def _GetRawFreePvInfo(cls, vg_name):
315 """Return info (size/free) about PVs. 316 317 @type vg_name: string 318 @param vg_name: VG name 319 @rtype: tuple 320 @return: (standard_pv_size_in_MiB, number_of_free_pvs, total_number_of_pvs) 321 322 """ 323 pvs_info = cls.GetPVInfo([vg_name]) 324 if not pvs_info: 325 pv_size = 0.0 326 free_pvs = 0 327 num_pvs = 0 328 else: 329 pv_size = cls._GetStdPvSize(pvs_info) 330 free_pvs = len(cls._GetEmptyPvNames(pvs_info)) 331 num_pvs = len(pvs_info) 332 return (pv_size, free_pvs, num_pvs)
333 334 @classmethod
335 - def _GetExclusiveStorageVgFree(cls, vg_name):
336 """Return the free disk space in the given VG, in exclusive storage mode. 337 338 @type vg_name: string 339 @param vg_name: VG name 340 @rtype: float 341 @return: free space in MiB 342 """ 343 (pv_size, free_pvs, _) = cls._GetRawFreePvInfo(vg_name) 344 return pv_size * free_pvs
345 346 @classmethod
347 - def GetVgSpindlesInfo(cls, vg_name):
348 """Get the free space info for specific VGs. 349 350 @param vg_name: volume group name 351 @rtype: tuple 352 @return: (free_spindles, total_spindles) 353 354 """ 355 (_, free_pvs, num_pvs) = cls._GetRawFreePvInfo(vg_name) 356 return (free_pvs, num_pvs)
357 358 @classmethod
359 - def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
360 """Get the free space info for specific VGs. 361 362 @param vg_names: list of volume group names, if empty all will be returned 363 @param excl_stor: whether exclusive_storage is enabled 364 @param filter_readonly: whether to skip over readonly VGs 365 366 @rtype: list 367 @return: list of tuples (free_space, total_size, name) with free_space in 368 MiB 369 370 """ 371 try: 372 info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr", 373 "vg_size"]) 374 except errors.GenericError, err: 375 logging.error("Can't get VG information: %s", err) 376 return None 377 378 data = [] 379 for vg_name, vg_free, vg_attr, vg_size in info: 380 # (possibly) skip over vgs which are not writable 381 if filter_readonly and vg_attr[0] == "r": 382 continue 383 # (possibly) skip over vgs which are not in the right volume group(s) 384 if vg_names and vg_name not in vg_names: 385 continue 386 # Exclusive storage needs a different concept of free space 387 if excl_stor: 388 es_free = cls._GetExclusiveStorageVgFree(vg_name) 389 assert es_free <= vg_free 390 vg_free = es_free 391 data.append((float(vg_free), float(vg_size), vg_name)) 392 393 return data
394 395 @classmethod
396 - def _ValidateName(cls, name):
397 """Validates that a given name is valid as VG or LV name. 398 399 The list of valid characters and restricted names is taken out of 400 the lvm(8) manpage, with the simplification that we enforce both 401 VG and LV restrictions on the names. 402 403 """ 404 if (not cls._VALID_NAME_RE.match(name) or 405 name in cls._INVALID_NAMES or 406 compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)): 407 base.ThrowError("Invalid LVM name '%s'", name)
408
409 - def Remove(self):
410 """Remove this logical volume. 411 412 """ 413 if not self.minor and not self.Attach(): 414 # the LV does not exist 415 return 416 result = utils.RunCmd(["lvremove", "-f", "%s/%s" % 417 (self._vg_name, self._lv_name)]) 418 if result.failed: 419 base.ThrowError("Can't lvremove: %s - %s", 420 result.fail_reason, result.output)
421
422 - def Rename(self, new_id):
423 """Rename this logical volume. 424 425 """ 426 if not isinstance(new_id, (tuple, list)) or len(new_id) != 2: 427 raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id) 428 new_vg, new_name = new_id 429 if new_vg != self._vg_name: 430 raise errors.ProgrammerError("Can't move a logical volume across" 431 " volume groups (from %s to to %s)" % 432 (self._vg_name, new_vg)) 433 result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name]) 434 if result.failed: 435 base.ThrowError("Failed to rename the logical volume: %s", result.output) 436 self._lv_name = new_name 437 self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
438 439 @classmethod
440 - def _ParseLvInfoLine(cls, line, sep):
441 """Parse one line of the lvs output used in L{_GetLvInfo}. 442 443 """ 444 elems = line.strip().split(sep) 445 446 # The previous iteration of code here assumed that LVM might put another 447 # separator to the right of the output. The PV info might be empty for 448 # thin volumes, so stripping off the separators might cut off the last 449 # empty element - do this instead. 450 if len(elems) == 7 and elems[-1] == "": 451 elems.pop() 452 453 if len(elems) != 6: 454 base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems)) 455 456 (status, major, minor, pe_size, stripes, pvs) = elems 457 if len(status) < 6: 458 base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status) 459 460 try: 461 major = int(major) 462 minor = int(minor) 463 except (TypeError, ValueError), err: 464 base.ThrowError("lvs major/minor cannot be parsed: %s", str(err)) 465 466 try: 467 pe_size = int(float(pe_size)) 468 except (TypeError, ValueError), err: 469 base.ThrowError("Can't parse vg extent size: %s", err) 470 471 try: 472 stripes = int(stripes) 473 except (TypeError, ValueError), err: 474 base.ThrowError("Can't parse the number of stripes: %s", err) 475 476 pv_names = [] 477 if pvs != "": 478 for pv in pvs.split(","): 479 m = re.match(cls._PARSE_PV_DEV_RE, pv) 480 if not m: 481 base.ThrowError("Can't parse this device list: %s", pvs) 482 pv_names.append(m.group(1)) 483 484 return (status, major, minor, pe_size, stripes, pv_names)
485 486 @classmethod
487 - def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd):
488 """Get info about the given existing LV to be used. 489 490 """ 491 sep = "|" 492 result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep, 493 "--units=k", "--nosuffix", 494 "-olv_attr,lv_kernel_major,lv_kernel_minor," 495 "vg_extent_size,stripes,devices", dev_path]) 496 if result.failed: 497 base.ThrowError("Can't find LV %s: %s, %s", 498 dev_path, result.fail_reason, result.output) 499 # the output can (and will) have multiple lines for multi-segment 500 # LVs, as the 'stripes' parameter is a segment one, so we take 501 # only the last entry, which is the one we're interested in; note 502 # that with LVM2 anyway the 'stripes' value must be constant 503 # across segments, so this is a no-op actually 504 out = result.stdout.splitlines() 505 if not out: # totally empty result? splitlines() returns at least 506 # one line for any non-empty string 507 base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out)) 508 pv_names = set() 509 for line in out: 510 (status, major, minor, pe_size, stripes, more_pvs) = \ 511 cls._ParseLvInfoLine(line, sep) 512 pv_names.update(more_pvs) 513 return (status, major, minor, pe_size, stripes, pv_names)
514
515 - def Attach(self):
516 """Attach to an existing LV. 517 518 This method will try to see if an existing and active LV exists 519 which matches our name. If so, its major/minor will be 520 recorded. 521 522 """ 523 self.attached = False 524 try: 525 (status, major, minor, pe_size, stripes, pv_names) = \ 526 self._GetLvInfo(self.dev_path) 527 except errors.BlockDeviceError: 528 return False 529 530 self.major = major 531 self.minor = minor 532 self.pe_size = pe_size 533 self.stripe_count = stripes 534 self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing 535 # storage 536 self.pv_names = pv_names 537 self.attached = True 538 return True
539
540 - def Assemble(self):
541 """Assemble the device. 542 543 We always run `lvchange -ay` on the LV to ensure it's active before 544 use, as there were cases when xenvg was not active after boot 545 (also possibly after disk issues). 546 547 """ 548 result = utils.RunCmd(["lvchange", "-ay", self.dev_path]) 549 if result.failed: 550 base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
551
552 - def Shutdown(self):
553 """Shutdown the device. 554 555 This is a no-op for the LV device type, as we don't deactivate the 556 volumes on shutdown. 557 558 """ 559 pass
560
561 - def GetSyncStatus(self):
562 """Returns the sync status of the device. 563 564 If this device is a mirroring device, this function returns the 565 status of the mirror. 566 567 For logical volumes, sync_percent and estimated_time are always 568 None (no recovery in progress, as we don't handle the mirrored LV 569 case). The is_degraded parameter is the inverse of the ldisk 570 parameter. 571 572 For the ldisk parameter, we check if the logical volume has the 573 'virtual' type, which means it's not backed by existing storage 574 anymore (read from it return I/O error). This happens after a 575 physical disk failure and subsequent 'vgreduce --removemissing' on 576 the volume group. 577 578 The status was already read in Attach, so we just return it. 579 580 @rtype: objects.BlockDevStatus 581 582 """ 583 if self._degraded: 584 ldisk_status = constants.LDS_FAULTY 585 else: 586 ldisk_status = constants.LDS_OKAY 587 588 return objects.BlockDevStatus(dev_path=self.dev_path, 589 major=self.major, 590 minor=self.minor, 591 sync_percent=None, 592 estimated_time=None, 593 is_degraded=self._degraded, 594 ldisk_status=ldisk_status)
595
596 - def Open(self, force=False, exclusive=True):
597 """Make the device ready for I/O. 598 599 This is a no-op for the LV device type. 600 601 """ 602 pass
603
604 - def Close(self):
605 """Notifies that the device will no longer be used for I/O. 606 607 This is a no-op for the LV device type. 608 609 """ 610 pass
611
612 - def Snapshot(self, snap_name=None, snap_size=None):
613 """Create a snapshot copy of an lvm block device. 614 615 @returns: tuple (vg, lv) 616 617 """ 618 if not snap_name: 619 snap_name = self._lv_name + ".snap" 620 621 if not snap_size: 622 # FIXME: choose a saner value for the snapshot size 623 # let's stay on the safe side and ask for the full size, for now 624 snap_size = self.size 625 626 # remove existing snapshot if found 627 snap = LogicalVolume((self._vg_name, snap_name), None, snap_size, 628 self.params, self.dyn_params) 629 base.IgnoreError(snap.Remove) 630 631 vg_info = self.GetVGInfo([self._vg_name], False) 632 if not vg_info: 633 base.ThrowError("Can't compute VG info for vg %s", self._vg_name) 634 free_size, _, _ = vg_info[0] 635 if free_size < snap_size: 636 base.ThrowError("Not enough free space: required %s," 637 " available %s", snap_size, free_size) 638 639 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % snap_size, "-s", 640 "-n%s" % snap_name, self.dev_path])) 641 642 return (self._vg_name, snap_name)
643
644 - def _RemoveOldInfo(self):
645 """Try to remove old tags from the lv. 646 647 """ 648 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix", 649 self.dev_path]) 650 _CheckResult(result) 651 652 raw_tags = result.stdout.strip() 653 if raw_tags: 654 for tag in raw_tags.split(","): 655 _CheckResult(utils.RunCmd(["lvchange", "--deltag", 656 tag.strip(), self.dev_path]))
657
658 - def SetInfo(self, text):
659 """Update metadata with info text. 660 661 """ 662 base.BlockDev.SetInfo(self, text) 663 664 self._RemoveOldInfo() 665 666 # Replace invalid characters 667 text = re.sub("^[^A-Za-z0-9_+.]", "_", text) 668 text = re.sub("[^-A-Za-z0-9_+.]", "_", text) 669 670 # Only up to 128 characters are allowed 671 text = text[:128] 672 673 _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
674
676 """Return how much the disk can grow with exclusive storage. 677 678 @rtype: float 679 @return: available space in Mib 680 681 """ 682 pvs_info = self.GetPVInfo([self._vg_name]) 683 if not pvs_info: 684 base.ThrowError("Cannot get information about PVs for %s", self.dev_path) 685 std_pv_size = self._GetStdPvSize(pvs_info) 686 free_space = sum(pvi.free - (pvi.size - std_pv_size) 687 for pvi in pvs_info 688 if pvi.name in self.pv_names) 689 return free_space
690
691 - def Grow(self, amount, dryrun, backingstore, excl_stor):
692 """Grow the logical volume. 693 694 """ 695 if not backingstore: 696 return 697 if self.pe_size is None or self.stripe_count is None: 698 if not self.Attach(): 699 base.ThrowError("Can't attach to LV during Grow()") 700 full_stripe_size = self.pe_size * self.stripe_count 701 # pe_size is in KB 702 amount *= 1024 703 rest = amount % full_stripe_size 704 if rest != 0: 705 amount += full_stripe_size - rest 706 cmd = ["lvextend", "-L", "+%dk" % amount] 707 if dryrun: 708 cmd.append("--test") 709 if excl_stor: 710 free_space = self._GetGrowthAvaliabilityExclStor() 711 # amount is in KiB, free_space in MiB 712 if amount > free_space * 1024: 713 base.ThrowError("Not enough free space to grow %s: %d MiB required," 714 " %d available", self.dev_path, amount / 1024, 715 free_space) 716 # Disk growth doesn't grow the number of spindles, so we must stay within 717 # our assigned volumes 718 pvlist = list(self.pv_names) 719 else: 720 pvlist = [] 721 # we try multiple algorithms since the 'best' ones might not have 722 # space available in the right place, but later ones might (since 723 # they have less constraints); also note that only recent LVM 724 # supports 'cling' 725 for alloc_policy in "contiguous", "cling", "normal": 726 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path] + 727 pvlist) 728 if not result.failed: 729 return 730 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
731
732 - def GetActualSpindles(self):
733 """Return the number of spindles used. 734 735 """ 736 assert self.attached, "BlockDevice not attached in GetActualSpindles()" 737 return len(self.pv_names)
738
739 740 -class PersistentBlockDevice(base.BlockDev):
741 """A block device with persistent node 742 743 May be either directly attached, or exposed through DM (e.g. dm-multipath). 744 udev helpers are probably required to give persistent, human-friendly 745 names. 746 747 For the time being, pathnames are required to lie under /dev. 748 749 """
750 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
751 """Attaches to a static block device. 752 753 The unique_id is a path under /dev. 754 755 """ 756 super(PersistentBlockDevice, self).__init__(unique_id, children, size, 757 params, dyn_params, *args) 758 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 759 raise ValueError("Invalid configuration data %s" % str(unique_id)) 760 self.dev_path = unique_id[1] 761 if not os.path.realpath(self.dev_path).startswith("/dev/"): 762 raise ValueError("Full path '%s' lies outside /dev" % 763 os.path.realpath(self.dev_path)) 764 # TODO: this is just a safety guard checking that we only deal with devices 765 # we know how to handle. In the future this will be integrated with 766 # external storage backends and possible values will probably be collected 767 # from the cluster configuration. 768 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL: 769 raise ValueError("Got persistent block device of invalid type: %s" % 770 unique_id[0]) 771 772 self.major = self.minor = None 773 self.Attach()
774 775 @classmethod
776 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 777 dyn_params, *args):
778 """Create a new device 779 780 This is a noop, we only return a PersistentBlockDevice instance 781 782 """ 783 if excl_stor: 784 raise errors.ProgrammerError("Persistent block device requested with" 785 " exclusive_storage") 786 return PersistentBlockDevice(unique_id, children, 0, params, dyn_params, 787 *args)
788
789 - def Remove(self):
790 """Remove a device 791 792 This is a noop 793 794 """ 795 pass
796
797 - def Rename(self, new_id):
798 """Rename this device. 799 800 """ 801 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
802
803 - def Attach(self):
804 """Attach to an existing block device. 805 806 807 """ 808 self.attached = False 809 try: 810 st = os.stat(self.dev_path) 811 except OSError, err: 812 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) 813 return False 814 815 if not stat.S_ISBLK(st.st_mode): 816 logging.error("%s is not a block device", self.dev_path) 817 return False 818 819 self.major = os.major(st.st_rdev) 820 self.minor = utils.osminor(st.st_rdev) 821 self.attached = True 822 823 return True
824
825 - def Assemble(self):
826 """Assemble the device. 827 828 """ 829 pass
830
831 - def Shutdown(self):
832 """Shutdown the device. 833 834 """ 835 pass
836
837 - def Open(self, force=False, exclusive=True):
838 """Make the device ready for I/O. 839 840 """ 841 pass
842
843 - def Close(self):
844 """Notifies that the device will no longer be used for I/O. 845 846 """ 847 pass
848
849 - def Grow(self, amount, dryrun, backingstore, excl_stor):
850 """Grow the logical volume. 851 852 """ 853 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
854
855 - def Import(self):
856 """Builds the shell command for importing data to device. 857 858 @see: L{BlockDev.Import} for details 859 860 """ 861 base.ThrowError("Importing data is not supported for the" 862 " PersistentBlockDevice template")
863
864 865 -class RADOSBlockDevice(base.BlockDev):
866 """A RADOS Block Device (rbd). 867 868 This class implements the RADOS Block Device for the backend. You need 869 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for 870 this to be functional. 871 872 """
873 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
874 """Attaches to an rbd device. 875 876 """ 877 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params, 878 dyn_params, *args) 879 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 880 raise ValueError("Invalid configuration data %s" % str(unique_id)) 881 882 self.driver, self.rbd_name = unique_id 883 self.rbd_pool = params[constants.LDP_POOL] 884 885 self.major = self.minor = None 886 self.Attach()
887 888 @classmethod
889 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 890 dyn_params, *args):
891 """Create a new rbd device. 892 893 Provision a new rbd volume inside a RADOS pool. 894 895 """ 896 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 897 raise errors.ProgrammerError("Invalid configuration data %s" % 898 str(unique_id)) 899 if excl_stor: 900 raise errors.ProgrammerError("RBD device requested with" 901 " exclusive_storage") 902 rbd_pool = params[constants.LDP_POOL] 903 rbd_name = unique_id[1] 904 905 # Provision a new rbd volume (Image) inside the RADOS cluster. 906 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool, 907 rbd_name, "--size", "%s" % size] 908 result = utils.RunCmd(cmd) 909 if result.failed: 910 base.ThrowError("rbd creation failed (%s): %s", 911 result.fail_reason, result.output) 912 913 return RADOSBlockDevice(unique_id, children, size, params, dyn_params, 914 *args)
915
916 - def Remove(self):
917 """Remove the rbd device. 918 919 """ 920 rbd_pool = self.params[constants.LDP_POOL] 921 rbd_name = self.unique_id[1] 922 923 if not self.minor and not self.Attach(): 924 # The rbd device doesn't exist. 925 return 926 927 # First shutdown the device (remove mappings). 928 self.Shutdown() 929 930 # Remove the actual Volume (Image) from the RADOS cluster. 931 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name] 932 result = utils.RunCmd(cmd) 933 if result.failed: 934 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s", 935 result.fail_reason, result.output)
936
937 - def Rename(self, new_id):
938 """Rename this device. 939 940 """ 941 pass
942
943 - def Attach(self):
944 """Attach to an existing rbd device. 945 946 This method maps the rbd volume that matches our name with 947 an rbd device and then attaches to this device. 948 949 """ 950 self.attached = False 951 952 # Map the rbd volume to a block device under /dev 953 self.dev_path = self._MapVolumeToBlockdev(self.unique_id) 954 955 try: 956 st = os.stat(self.dev_path) 957 except OSError, err: 958 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) 959 return False 960 961 if not stat.S_ISBLK(st.st_mode): 962 logging.error("%s is not a block device", self.dev_path) 963 return False 964 965 self.major = os.major(st.st_rdev) 966 self.minor = utils.osminor(st.st_rdev) 967 self.attached = True 968 969 return True
970
971 - def _MapVolumeToBlockdev(self, unique_id):
972 """Maps existing rbd volumes to block devices. 973 974 This method should be idempotent if the mapping already exists. 975 976 @rtype: string 977 @return: the block device path that corresponds to the volume 978 979 """ 980 pool = self.params[constants.LDP_POOL] 981 name = unique_id[1] 982 983 # Check if the mapping already exists. 984 rbd_dev = self._VolumeToBlockdev(pool, name) 985 if rbd_dev: 986 # The mapping exists. Return it. 987 return rbd_dev 988 989 # The mapping doesn't exist. Create it. 990 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name] 991 result = utils.RunCmd(map_cmd) 992 if result.failed: 993 base.ThrowError("rbd map failed (%s): %s", 994 result.fail_reason, result.output) 995 996 # Find the corresponding rbd device. 997 rbd_dev = self._VolumeToBlockdev(pool, name) 998 if not rbd_dev: 999 base.ThrowError("rbd map succeeded, but could not find the rbd block" 1000 " device in output of showmapped, for volume: %s", name) 1001 1002 # The device was successfully mapped. Return it. 1003 return rbd_dev
1004 1005 @classmethod
1006 - def _VolumeToBlockdev(cls, pool, volume_name):
1007 """Do the 'volume name'-to-'rbd block device' resolving. 1008 1009 @type pool: string 1010 @param pool: RADOS pool to use 1011 @type volume_name: string 1012 @param volume_name: the name of the volume whose device we search for 1013 @rtype: string or None 1014 @return: block device path if the volume is mapped, else None 1015 1016 """ 1017 try: 1018 # Newer versions of the rbd tool support json output formatting. Use it 1019 # if available. 1020 showmap_cmd = [ 1021 constants.RBD_CMD, 1022 "showmapped", 1023 "-p", 1024 pool, 1025 "--format", 1026 "json" 1027 ] 1028 result = utils.RunCmd(showmap_cmd) 1029 if result.failed: 1030 logging.error("rbd JSON output formatting returned error (%s): %s," 1031 "falling back to plain output parsing", 1032 result.fail_reason, result.output) 1033 raise RbdShowmappedJsonError 1034 1035 return cls._ParseRbdShowmappedJson(result.output, volume_name) 1036 except RbdShowmappedJsonError: 1037 # For older versions of rbd, we have to parse the plain / text output 1038 # manually. 1039 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool] 1040 result = utils.RunCmd(showmap_cmd) 1041 if result.failed: 1042 base.ThrowError("rbd showmapped failed (%s): %s", 1043 result.fail_reason, result.output) 1044 1045 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1046 1047 @staticmethod
1048 - def _ParseRbdShowmappedJson(output, volume_name):
1049 """Parse the json output of `rbd showmapped'. 1050 1051 This method parses the json output of `rbd showmapped' and returns the rbd 1052 block device path (e.g. /dev/rbd0) that matches the given rbd volume. 1053 1054 @type output: string 1055 @param output: the json output of `rbd showmapped' 1056 @type volume_name: string 1057 @param volume_name: the name of the volume whose device we search for 1058 @rtype: string or None 1059 @return: block device path if the volume is mapped, else None 1060 1061 """ 1062 try: 1063 devices = serializer.LoadJson(output) 1064 except ValueError, err: 1065 base.ThrowError("Unable to parse JSON data: %s" % err) 1066 1067 rbd_dev = None 1068 for d in devices.values(): # pylint: disable=E1103 1069 try: 1070 name = d["name"] 1071 except KeyError: 1072 base.ThrowError("'name' key missing from json object %s", devices) 1073 1074 if name == volume_name: 1075 if rbd_dev is not None: 1076 base.ThrowError("rbd volume %s is mapped more than once", volume_name) 1077 1078 rbd_dev = d["device"] 1079 1080 return rbd_dev
1081 1082 @staticmethod
1083 - def _ParseRbdShowmappedPlain(output, volume_name):
1084 """Parse the (plain / text) output of `rbd showmapped'. 1085 1086 This method parses the output of `rbd showmapped' and returns 1087 the rbd block device path (e.g. /dev/rbd0) that matches the 1088 given rbd volume. 1089 1090 @type output: string 1091 @param output: the plain text output of `rbd showmapped' 1092 @type volume_name: string 1093 @param volume_name: the name of the volume whose device we search for 1094 @rtype: string or None 1095 @return: block device path if the volume is mapped, else None 1096 1097 """ 1098 allfields = 5 1099 volumefield = 2 1100 devicefield = 4 1101 1102 lines = output.splitlines() 1103 1104 # Try parsing the new output format (ceph >= 0.55). 1105 splitted_lines = map(lambda l: l.split(), lines) 1106 1107 # Check for empty output. 1108 if not splitted_lines: 1109 return None 1110 1111 # Check showmapped output, to determine number of fields. 1112 field_cnt = len(splitted_lines[0]) 1113 if field_cnt != allfields: 1114 # Parsing the new format failed. Fallback to parsing the old output 1115 # format (< 0.55). 1116 splitted_lines = map(lambda l: l.split("\t"), lines) 1117 if field_cnt != allfields: 1118 base.ThrowError("Cannot parse rbd showmapped output expected %s fields," 1119 " found %s", allfields, field_cnt) 1120 1121 matched_lines = \ 1122 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name, 1123 splitted_lines) 1124 1125 if len(matched_lines) > 1: 1126 base.ThrowError("rbd volume %s mapped more than once", volume_name) 1127 1128 if matched_lines: 1129 # rbd block device found. Return it. 1130 rbd_dev = matched_lines[0][devicefield] 1131 return rbd_dev 1132 1133 # The given volume is not mapped. 1134 return None
1135
1136 - def Assemble(self):
1137 """Assemble the device. 1138 1139 """ 1140 pass
1141
1142 - def Shutdown(self):
1143 """Shutdown the device. 1144 1145 """ 1146 if not self.minor and not self.Attach(): 1147 # The rbd device doesn't exist. 1148 return 1149 1150 # Unmap the block device from the Volume. 1151 self._UnmapVolumeFromBlockdev(self.unique_id) 1152 1153 self.minor = None 1154 self.dev_path = None
1155
1156 - def _UnmapVolumeFromBlockdev(self, unique_id):
1157 """Unmaps the rbd device from the Volume it is mapped. 1158 1159 Unmaps the rbd device from the Volume it was previously mapped to. 1160 This method should be idempotent if the Volume isn't mapped. 1161 1162 """ 1163 pool = self.params[constants.LDP_POOL] 1164 name = unique_id[1] 1165 1166 # Check if the mapping already exists. 1167 rbd_dev = self._VolumeToBlockdev(pool, name) 1168 1169 if rbd_dev: 1170 # The mapping exists. Unmap the rbd device. 1171 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev] 1172 result = utils.RunCmd(unmap_cmd) 1173 if result.failed: 1174 base.ThrowError("rbd unmap failed (%s): %s", 1175 result.fail_reason, result.output)
1176
1177 - def Open(self, force=False, exclusive=True):
1178 """Make the device ready for I/O. 1179 1180 """ 1181 pass
1182
1183 - def Close(self):
1184 """Notifies that the device will no longer be used for I/O. 1185 1186 """ 1187 pass
1188
1189 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1190 """Grow the Volume. 1191 1192 @type amount: integer 1193 @param amount: the amount (in mebibytes) to grow with 1194 @type dryrun: boolean 1195 @param dryrun: whether to execute the operation in simulation mode 1196 only, without actually increasing the size 1197 1198 """ 1199 if not backingstore: 1200 return 1201 if not self.Attach(): 1202 base.ThrowError("Can't attach to rbd device during Grow()") 1203 1204 if dryrun: 1205 # the rbd tool does not support dry runs of resize operations. 1206 # Since rbd volumes are thinly provisioned, we assume 1207 # there is always enough free space for the operation. 1208 return 1209 1210 rbd_pool = self.params[constants.LDP_POOL] 1211 rbd_name = self.unique_id[1] 1212 new_size = self.size + amount 1213 1214 # Resize the rbd volume (Image) inside the RADOS cluster. 1215 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool, 1216 rbd_name, "--size", "%s" % new_size] 1217 result = utils.RunCmd(cmd) 1218 if result.failed: 1219 base.ThrowError("rbd resize failed (%s): %s", 1220 result.fail_reason, result.output)
1221
1222 - def Import(self):
1223 """Builds the shell command for importing data to device. 1224 1225 @see: L{BlockDev.Import} for details 1226 1227 """ 1228 if not self.minor and not self.Attach(): 1229 # The rbd device doesn't exist. 1230 base.ThrowError("Can't attach to rbd device during Import()") 1231 1232 rbd_pool = self.params[constants.LDP_POOL] 1233 rbd_name = self.unique_id[1] 1234 1235 # Currently, the 'rbd import' command imports data only to non-existing 1236 # volumes. If the rbd volume exists the command will fail. 1237 # The disk conversion mechanism though, has already created the new rbd 1238 # volume at the time we perform the data copy, so we have to first remove 1239 # the volume before starting to import its data. The 'rbd import' will 1240 # re-create the rbd volume. We choose to remove manually the rbd device 1241 # instead of calling its 'Remove()' method to avoid affecting the 'self.' 1242 # parameters of the device. Also, this part of the removal code will go 1243 # away once 'rbd import' has support for importing into an existing volume. 1244 # TODO: update this method when the 'rbd import' command supports the 1245 # '--force' option, which will allow importing to an existing volume. 1246 1247 # Unmap the block device from the Volume. 1248 self._UnmapVolumeFromBlockdev(self.unique_id) 1249 1250 # Remove the actual Volume (Image) from the RADOS cluster. 1251 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name] 1252 result = utils.RunCmd(cmd) 1253 if result.failed: 1254 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s", 1255 result.fail_reason, result.output) 1256 1257 # We use "-" for importing from stdin 1258 return [constants.RBD_CMD, "import", 1259 "-p", rbd_pool, 1260 "-", rbd_name]
1261
1262 - def Export(self):
1263 """Builds the shell command for exporting data from device. 1264 1265 @see: L{BlockDev.Export} for details 1266 1267 """ 1268 if not self.minor and not self.Attach(): 1269 # The rbd device doesn't exist. 1270 base.ThrowError("Can't attach to rbd device during Export()") 1271 1272 rbd_pool = self.params[constants.LDP_POOL] 1273 rbd_name = self.unique_id[1] 1274 1275 # We use "-" for exporting to stdout. 1276 return [constants.RBD_CMD, "export", 1277 "-p", rbd_pool, 1278 rbd_name, "-"]
1279
1280 - def GetUserspaceAccessUri(self, hypervisor):
1281 """Generate KVM userspace URIs to be used as `-drive file` settings. 1282 1283 @see: L{BlockDev.GetUserspaceAccessUri} 1284 1285 """ 1286 if hypervisor == constants.HT_KVM: 1287 return "rbd:" + self.rbd_pool + "/" + self.rbd_name 1288 else: 1289 base.ThrowError("Hypervisor %s doesn't support RBD userspace access" % 1290 hypervisor)
1291
1292 1293 -def _VerifyDiskType(dev_type):
1294 if dev_type not in DEV_MAP: 1295 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1296
1297 1298 -def _VerifyDiskParams(disk):
1299 """Verifies if all disk parameters are set. 1300 1301 """ 1302 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params) 1303 if missing: 1304 raise errors.ProgrammerError("Block device is missing disk parameters: %s" % 1305 missing)
1306
1307 1308 -def FindDevice(disk, children):
1309 """Search for an existing, assembled device. 1310 1311 This will succeed only if the device exists and is assembled, but it 1312 does not do any actions in order to activate the device. 1313 1314 @type disk: L{objects.Disk} 1315 @param disk: the disk object to find 1316 @type children: list of L{bdev.BlockDev} 1317 @param children: the list of block devices that are children of the device 1318 represented by the disk parameter 1319 1320 """ 1321 _VerifyDiskType(disk.dev_type) 1322 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size, 1323 disk.params, disk.dynamic_params, 1324 disk.name, disk.uuid) 1325 if not device.attached: 1326 return None 1327 return device
1328
1329 1330 -def Assemble(disk, children):
1331 """Try to attach or assemble an existing device. 1332 1333 This will attach to assemble the device, as needed, to bring it 1334 fully up. It must be safe to run on already-assembled devices. 1335 1336 @type disk: L{objects.Disk} 1337 @param disk: the disk object to assemble 1338 @type children: list of L{bdev.BlockDev} 1339 @param children: the list of block devices that are children of the device 1340 represented by the disk parameter 1341 1342 """ 1343 _VerifyDiskType(disk.dev_type) 1344 _VerifyDiskParams(disk) 1345 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size, 1346 disk.params, disk.dynamic_params, 1347 disk.name, disk.uuid) 1348 device.Assemble() 1349 return device
1350
1351 1352 -def Create(disk, children, excl_stor):
1353 """Create a device. 1354 1355 @type disk: L{objects.Disk} 1356 @param disk: the disk object to create 1357 @type children: list of L{bdev.BlockDev} 1358 @param children: the list of block devices that are children of the device 1359 represented by the disk parameter 1360 @type excl_stor: boolean 1361 @param excl_stor: Whether exclusive_storage is active 1362 @rtype: L{bdev.BlockDev} 1363 @return: the created device, or C{None} in case of an error 1364 1365 """ 1366 _VerifyDiskType(disk.dev_type) 1367 _VerifyDiskParams(disk) 1368 device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size, 1369 disk.spindles, disk.params, excl_stor, 1370 disk.dynamic_params, 1371 disk.name, disk.uuid) 1372 return device
1373 1374 # Please keep this at the bottom of the file for visibility. 1375 DEV_MAP = { 1376 constants.DT_PLAIN: LogicalVolume, 1377 constants.DT_DRBD8: drbd.DRBD8Dev, 1378 constants.DT_BLOCK: PersistentBlockDevice, 1379 constants.DT_RBD: RADOSBlockDevice, 1380 constants.DT_EXT: ExtStorageDevice, 1381 constants.DT_FILE: FileStorage, 1382 constants.DT_SHARED_FILE: FileStorage, 1383 constants.DT_GLUSTER: GlusterStorage, 1384 } 1385 """Map disk types to disk type classes. 1386 1387 @see: L{Assemble}, L{FindDevice}, L{Create}.""" # pylint: disable=W0105 1388