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 pathutils 
  47  from ganeti import serializer 
  48  from ganeti.storage import base 
  49  from ganeti.storage import drbd 
  50  from ganeti.storage.filestorage import FileStorage 
  51  from ganeti.storage.gluster import GlusterStorage 
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):
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, size):
613 """Create a snapshot copy of an lvm block device. 614 615 @returns: tuple (vg, lv) 616 617 """ 618 snap_name = self._lv_name + ".snap" 619 620 # remove existing snapshot if found 621 snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params, 622 self.dyn_params) 623 base.IgnoreError(snap.Remove) 624 625 vg_info = self.GetVGInfo([self._vg_name], False) 626 if not vg_info: 627 base.ThrowError("Can't compute VG info for vg %s", self._vg_name) 628 free_size, _, _ = vg_info[0] 629 if free_size < size: 630 base.ThrowError("Not enough free space: required %s," 631 " available %s", size, free_size) 632 633 _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s", 634 "-n%s" % snap_name, self.dev_path])) 635 636 return (self._vg_name, snap_name)
637
638 - def _RemoveOldInfo(self):
639 """Try to remove old tags from the lv. 640 641 """ 642 result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix", 643 self.dev_path]) 644 _CheckResult(result) 645 646 raw_tags = result.stdout.strip() 647 if raw_tags: 648 for tag in raw_tags.split(","): 649 _CheckResult(utils.RunCmd(["lvchange", "--deltag", 650 tag.strip(), self.dev_path]))
651
652 - def SetInfo(self, text):
653 """Update metadata with info text. 654 655 """ 656 base.BlockDev.SetInfo(self, text) 657 658 self._RemoveOldInfo() 659 660 # Replace invalid characters 661 text = re.sub("^[^A-Za-z0-9_+.]", "_", text) 662 text = re.sub("[^-A-Za-z0-9_+.]", "_", text) 663 664 # Only up to 128 characters are allowed 665 text = text[:128] 666 667 _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
668
670 """Return how much the disk can grow with exclusive storage. 671 672 @rtype: float 673 @return: available space in Mib 674 675 """ 676 pvs_info = self.GetPVInfo([self._vg_name]) 677 if not pvs_info: 678 base.ThrowError("Cannot get information about PVs for %s", self.dev_path) 679 std_pv_size = self._GetStdPvSize(pvs_info) 680 free_space = sum(pvi.free - (pvi.size - std_pv_size) 681 for pvi in pvs_info 682 if pvi.name in self.pv_names) 683 return free_space
684
685 - def Grow(self, amount, dryrun, backingstore, excl_stor):
686 """Grow the logical volume. 687 688 """ 689 if not backingstore: 690 return 691 if self.pe_size is None or self.stripe_count is None: 692 if not self.Attach(): 693 base.ThrowError("Can't attach to LV during Grow()") 694 full_stripe_size = self.pe_size * self.stripe_count 695 # pe_size is in KB 696 amount *= 1024 697 rest = amount % full_stripe_size 698 if rest != 0: 699 amount += full_stripe_size - rest 700 cmd = ["lvextend", "-L", "+%dk" % amount] 701 if dryrun: 702 cmd.append("--test") 703 if excl_stor: 704 free_space = self._GetGrowthAvaliabilityExclStor() 705 # amount is in KiB, free_space in MiB 706 if amount > free_space * 1024: 707 base.ThrowError("Not enough free space to grow %s: %d MiB required," 708 " %d available", self.dev_path, amount / 1024, 709 free_space) 710 # Disk growth doesn't grow the number of spindles, so we must stay within 711 # our assigned volumes 712 pvlist = list(self.pv_names) 713 else: 714 pvlist = [] 715 # we try multiple algorithms since the 'best' ones might not have 716 # space available in the right place, but later ones might (since 717 # they have less constraints); also note that only recent LVM 718 # supports 'cling' 719 for alloc_policy in "contiguous", "cling", "normal": 720 result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path] + 721 pvlist) 722 if not result.failed: 723 return 724 base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
725
726 - def GetActualSpindles(self):
727 """Return the number of spindles used. 728 729 """ 730 assert self.attached, "BlockDevice not attached in GetActualSpindles()" 731 return len(self.pv_names)
732
733 734 -class PersistentBlockDevice(base.BlockDev):
735 """A block device with persistent node 736 737 May be either directly attached, or exposed through DM (e.g. dm-multipath). 738 udev helpers are probably required to give persistent, human-friendly 739 names. 740 741 For the time being, pathnames are required to lie under /dev. 742 743 """
744 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
745 """Attaches to a static block device. 746 747 The unique_id is a path under /dev. 748 749 """ 750 super(PersistentBlockDevice, self).__init__(unique_id, children, size, 751 params, dyn_params, *args) 752 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 753 raise ValueError("Invalid configuration data %s" % str(unique_id)) 754 self.dev_path = unique_id[1] 755 if not os.path.realpath(self.dev_path).startswith("/dev/"): 756 raise ValueError("Full path '%s' lies outside /dev" % 757 os.path.realpath(self.dev_path)) 758 # TODO: this is just a safety guard checking that we only deal with devices 759 # we know how to handle. In the future this will be integrated with 760 # external storage backends and possible values will probably be collected 761 # from the cluster configuration. 762 if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL: 763 raise ValueError("Got persistent block device of invalid type: %s" % 764 unique_id[0]) 765 766 self.major = self.minor = None 767 self.Attach()
768 769 @classmethod
770 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 771 dyn_params, *args):
772 """Create a new device 773 774 This is a noop, we only return a PersistentBlockDevice instance 775 776 """ 777 if excl_stor: 778 raise errors.ProgrammerError("Persistent block device requested with" 779 " exclusive_storage") 780 return PersistentBlockDevice(unique_id, children, 0, params, dyn_params, 781 *args)
782
783 - def Remove(self):
784 """Remove a device 785 786 This is a noop 787 788 """ 789 pass
790
791 - def Rename(self, new_id):
792 """Rename this device. 793 794 """ 795 base.ThrowError("Rename is not supported for PersistentBlockDev storage")
796
797 - def Attach(self):
798 """Attach to an existing block device. 799 800 801 """ 802 self.attached = False 803 try: 804 st = os.stat(self.dev_path) 805 except OSError, err: 806 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) 807 return False 808 809 if not stat.S_ISBLK(st.st_mode): 810 logging.error("%s is not a block device", self.dev_path) 811 return False 812 813 self.major = os.major(st.st_rdev) 814 self.minor = utils.osminor(st.st_rdev) 815 self.attached = True 816 817 return True
818
819 - def Assemble(self):
820 """Assemble the device. 821 822 """ 823 pass
824
825 - def Shutdown(self):
826 """Shutdown the device. 827 828 """ 829 pass
830
831 - def Open(self, force=False):
832 """Make the device ready for I/O. 833 834 """ 835 pass
836
837 - def Close(self):
838 """Notifies that the device will no longer be used for I/O. 839 840 """ 841 pass
842
843 - def Grow(self, amount, dryrun, backingstore, excl_stor):
844 """Grow the logical volume. 845 846 """ 847 base.ThrowError("Grow is not supported for PersistentBlockDev storage")
848
849 850 -class RADOSBlockDevice(base.BlockDev):
851 """A RADOS Block Device (rbd). 852 853 This class implements the RADOS Block Device for the backend. You need 854 the rbd kernel driver, the RADOS Tools and a working RADOS cluster for 855 this to be functional. 856 857 """
858 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
859 """Attaches to an rbd device. 860 861 """ 862 super(RADOSBlockDevice, self).__init__(unique_id, children, size, params, 863 dyn_params, *args) 864 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 865 raise ValueError("Invalid configuration data %s" % str(unique_id)) 866 867 self.driver, self.rbd_name = unique_id 868 self.rbd_pool = params[constants.LDP_POOL] 869 870 self.major = self.minor = None 871 self.Attach()
872 873 @classmethod
874 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 875 dyn_params, *args):
876 """Create a new rbd device. 877 878 Provision a new rbd volume inside a RADOS pool. 879 880 """ 881 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 882 raise errors.ProgrammerError("Invalid configuration data %s" % 883 str(unique_id)) 884 if excl_stor: 885 raise errors.ProgrammerError("RBD device requested with" 886 " exclusive_storage") 887 rbd_pool = params[constants.LDP_POOL] 888 rbd_name = unique_id[1] 889 890 # Provision a new rbd volume (Image) inside the RADOS cluster. 891 cmd = [constants.RBD_CMD, "create", "-p", rbd_pool, 892 rbd_name, "--size", "%s" % size] 893 result = utils.RunCmd(cmd) 894 if result.failed: 895 base.ThrowError("rbd creation failed (%s): %s", 896 result.fail_reason, result.output) 897 898 return RADOSBlockDevice(unique_id, children, size, params, dyn_params, 899 *args)
900
901 - def Remove(self):
902 """Remove the rbd device. 903 904 """ 905 rbd_pool = self.params[constants.LDP_POOL] 906 rbd_name = self.unique_id[1] 907 908 if not self.minor and not self.Attach(): 909 # The rbd device doesn't exist. 910 return 911 912 # First shutdown the device (remove mappings). 913 self.Shutdown() 914 915 # Remove the actual Volume (Image) from the RADOS cluster. 916 cmd = [constants.RBD_CMD, "rm", "-p", rbd_pool, rbd_name] 917 result = utils.RunCmd(cmd) 918 if result.failed: 919 base.ThrowError("Can't remove Volume from cluster with rbd rm: %s - %s", 920 result.fail_reason, result.output)
921
922 - def Rename(self, new_id):
923 """Rename this device. 924 925 """ 926 pass
927
928 - def Attach(self):
929 """Attach to an existing rbd device. 930 931 This method maps the rbd volume that matches our name with 932 an rbd device and then attaches to this device. 933 934 """ 935 self.attached = False 936 937 # Map the rbd volume to a block device under /dev 938 self.dev_path = self._MapVolumeToBlockdev(self.unique_id) 939 940 try: 941 st = os.stat(self.dev_path) 942 except OSError, err: 943 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) 944 return False 945 946 if not stat.S_ISBLK(st.st_mode): 947 logging.error("%s is not a block device", self.dev_path) 948 return False 949 950 self.major = os.major(st.st_rdev) 951 self.minor = utils.osminor(st.st_rdev) 952 self.attached = True 953 954 return True
955
956 - def _MapVolumeToBlockdev(self, unique_id):
957 """Maps existing rbd volumes to block devices. 958 959 This method should be idempotent if the mapping already exists. 960 961 @rtype: string 962 @return: the block device path that corresponds to the volume 963 964 """ 965 pool = self.params[constants.LDP_POOL] 966 name = unique_id[1] 967 968 # Check if the mapping already exists. 969 rbd_dev = self._VolumeToBlockdev(pool, name) 970 if rbd_dev: 971 # The mapping exists. Return it. 972 return rbd_dev 973 974 # The mapping doesn't exist. Create it. 975 map_cmd = [constants.RBD_CMD, "map", "-p", pool, name] 976 result = utils.RunCmd(map_cmd) 977 if result.failed: 978 base.ThrowError("rbd map failed (%s): %s", 979 result.fail_reason, result.output) 980 981 # Find the corresponding rbd device. 982 rbd_dev = self._VolumeToBlockdev(pool, name) 983 if not rbd_dev: 984 base.ThrowError("rbd map succeeded, but could not find the rbd block" 985 " device in output of showmapped, for volume: %s", name) 986 987 # The device was successfully mapped. Return it. 988 return rbd_dev
989 990 @classmethod
991 - def _VolumeToBlockdev(cls, pool, volume_name):
992 """Do the 'volume name'-to-'rbd block device' resolving. 993 994 @type pool: string 995 @param pool: RADOS pool to use 996 @type volume_name: string 997 @param volume_name: the name of the volume whose device we search for 998 @rtype: string or None 999 @return: block device path if the volume is mapped, else None 1000 1001 """ 1002 try: 1003 # Newer versions of the rbd tool support json output formatting. Use it 1004 # if available. 1005 showmap_cmd = [ 1006 constants.RBD_CMD, 1007 "showmapped", 1008 "-p", 1009 pool, 1010 "--format", 1011 "json" 1012 ] 1013 result = utils.RunCmd(showmap_cmd) 1014 if result.failed: 1015 logging.error("rbd JSON output formatting returned error (%s): %s," 1016 "falling back to plain output parsing", 1017 result.fail_reason, result.output) 1018 raise RbdShowmappedJsonError 1019 1020 return cls._ParseRbdShowmappedJson(result.output, volume_name) 1021 except RbdShowmappedJsonError: 1022 # For older versions of rbd, we have to parse the plain / text output 1023 # manually. 1024 showmap_cmd = [constants.RBD_CMD, "showmapped", "-p", pool] 1025 result = utils.RunCmd(showmap_cmd) 1026 if result.failed: 1027 base.ThrowError("rbd showmapped failed (%s): %s", 1028 result.fail_reason, result.output) 1029 1030 return cls._ParseRbdShowmappedPlain(result.output, volume_name)
1031 1032 @staticmethod
1033 - def _ParseRbdShowmappedJson(output, volume_name):
1034 """Parse the json output of `rbd showmapped'. 1035 1036 This method parses the json output of `rbd showmapped' and returns the rbd 1037 block device path (e.g. /dev/rbd0) that matches the given rbd volume. 1038 1039 @type output: string 1040 @param output: the json output of `rbd showmapped' 1041 @type volume_name: string 1042 @param volume_name: the name of the volume whose device we search for 1043 @rtype: string or None 1044 @return: block device path if the volume is mapped, else None 1045 1046 """ 1047 try: 1048 devices = serializer.LoadJson(output) 1049 except ValueError, err: 1050 base.ThrowError("Unable to parse JSON data: %s" % err) 1051 1052 rbd_dev = None 1053 for d in devices.values(): # pylint: disable=E1103 1054 try: 1055 name = d["name"] 1056 except KeyError: 1057 base.ThrowError("'name' key missing from json object %s", devices) 1058 1059 if name == volume_name: 1060 if rbd_dev is not None: 1061 base.ThrowError("rbd volume %s is mapped more than once", volume_name) 1062 1063 rbd_dev = d["device"] 1064 1065 return rbd_dev
1066 1067 @staticmethod
1068 - def _ParseRbdShowmappedPlain(output, volume_name):
1069 """Parse the (plain / text) output of `rbd showmapped'. 1070 1071 This method parses the output of `rbd showmapped' and returns 1072 the rbd block device path (e.g. /dev/rbd0) that matches the 1073 given rbd volume. 1074 1075 @type output: string 1076 @param output: the plain text output of `rbd showmapped' 1077 @type volume_name: string 1078 @param volume_name: the name of the volume whose device we search for 1079 @rtype: string or None 1080 @return: block device path if the volume is mapped, else None 1081 1082 """ 1083 allfields = 5 1084 volumefield = 2 1085 devicefield = 4 1086 1087 lines = output.splitlines() 1088 1089 # Try parsing the new output format (ceph >= 0.55). 1090 splitted_lines = map(lambda l: l.split(), lines) 1091 1092 # Check for empty output. 1093 if not splitted_lines: 1094 return None 1095 1096 # Check showmapped output, to determine number of fields. 1097 field_cnt = len(splitted_lines[0]) 1098 if field_cnt != allfields: 1099 # Parsing the new format failed. Fallback to parsing the old output 1100 # format (< 0.55). 1101 splitted_lines = map(lambda l: l.split("\t"), lines) 1102 if field_cnt != allfields: 1103 base.ThrowError("Cannot parse rbd showmapped output expected %s fields," 1104 " found %s", allfields, field_cnt) 1105 1106 matched_lines = \ 1107 filter(lambda l: len(l) == allfields and l[volumefield] == volume_name, 1108 splitted_lines) 1109 1110 if len(matched_lines) > 1: 1111 base.ThrowError("rbd volume %s mapped more than once", volume_name) 1112 1113 if matched_lines: 1114 # rbd block device found. Return it. 1115 rbd_dev = matched_lines[0][devicefield] 1116 return rbd_dev 1117 1118 # The given volume is not mapped. 1119 return None
1120
1121 - def Assemble(self):
1122 """Assemble the device. 1123 1124 """ 1125 pass
1126
1127 - def Shutdown(self):
1128 """Shutdown the device. 1129 1130 """ 1131 if not self.minor and not self.Attach(): 1132 # The rbd device doesn't exist. 1133 return 1134 1135 # Unmap the block device from the Volume. 1136 self._UnmapVolumeFromBlockdev(self.unique_id) 1137 1138 self.minor = None 1139 self.dev_path = None
1140
1141 - def _UnmapVolumeFromBlockdev(self, unique_id):
1142 """Unmaps the rbd device from the Volume it is mapped. 1143 1144 Unmaps the rbd device from the Volume it was previously mapped to. 1145 This method should be idempotent if the Volume isn't mapped. 1146 1147 """ 1148 pool = self.params[constants.LDP_POOL] 1149 name = unique_id[1] 1150 1151 # Check if the mapping already exists. 1152 rbd_dev = self._VolumeToBlockdev(pool, name) 1153 1154 if rbd_dev: 1155 # The mapping exists. Unmap the rbd device. 1156 unmap_cmd = [constants.RBD_CMD, "unmap", "%s" % rbd_dev] 1157 result = utils.RunCmd(unmap_cmd) 1158 if result.failed: 1159 base.ThrowError("rbd unmap failed (%s): %s", 1160 result.fail_reason, result.output)
1161
1162 - def Open(self, force=False):
1163 """Make the device ready for I/O. 1164 1165 """ 1166 pass
1167
1168 - def Close(self):
1169 """Notifies that the device will no longer be used for I/O. 1170 1171 """ 1172 pass
1173
1174 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1175 """Grow the Volume. 1176 1177 @type amount: integer 1178 @param amount: the amount (in mebibytes) to grow with 1179 @type dryrun: boolean 1180 @param dryrun: whether to execute the operation in simulation mode 1181 only, without actually increasing the size 1182 1183 """ 1184 if not backingstore: 1185 return 1186 if not self.Attach(): 1187 base.ThrowError("Can't attach to rbd device during Grow()") 1188 1189 if dryrun: 1190 # the rbd tool does not support dry runs of resize operations. 1191 # Since rbd volumes are thinly provisioned, we assume 1192 # there is always enough free space for the operation. 1193 return 1194 1195 rbd_pool = self.params[constants.LDP_POOL] 1196 rbd_name = self.unique_id[1] 1197 new_size = self.size + amount 1198 1199 # Resize the rbd volume (Image) inside the RADOS cluster. 1200 cmd = [constants.RBD_CMD, "resize", "-p", rbd_pool, 1201 rbd_name, "--size", "%s" % new_size] 1202 result = utils.RunCmd(cmd) 1203 if result.failed: 1204 base.ThrowError("rbd resize failed (%s): %s", 1205 result.fail_reason, result.output)
1206
1207 - def GetUserspaceAccessUri(self, hypervisor):
1208 """Generate KVM userspace URIs to be used as `-drive file` settings. 1209 1210 @see: L{BlockDev.GetUserspaceAccessUri} 1211 1212 """ 1213 if hypervisor == constants.HT_KVM: 1214 return "rbd:" + self.rbd_pool + "/" + self.rbd_name 1215 else: 1216 base.ThrowError("Hypervisor %s doesn't support RBD userspace access" % 1217 hypervisor)
1218
1219 1220 -class ExtStorageDevice(base.BlockDev):
1221 """A block device provided by an ExtStorage Provider. 1222 1223 This class implements the External Storage Interface, which means 1224 handling of the externally provided block devices. 1225 1226 """
1227 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
1228 """Attaches to an extstorage block device. 1229 1230 """ 1231 super(ExtStorageDevice, self).__init__(unique_id, children, size, params, 1232 dyn_params, *args) 1233 (self.name, self.uuid) = args 1234 1235 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 1236 raise ValueError("Invalid configuration data %s" % str(unique_id)) 1237 1238 self.driver, self.vol_name = unique_id 1239 self.ext_params = params 1240 1241 self.major = self.minor = None 1242 self.Attach()
1243 1244 @classmethod
1245 - def Create(cls, unique_id, children, size, spindles, params, excl_stor, 1246 dyn_params, *args):
1247 """Create a new extstorage device. 1248 1249 Provision a new volume using an extstorage provider, which will 1250 then be mapped to a block device. 1251 1252 """ 1253 (name, uuid) = args 1254 1255 if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2: 1256 raise errors.ProgrammerError("Invalid configuration data %s" % 1257 str(unique_id)) 1258 if excl_stor: 1259 raise errors.ProgrammerError("extstorage device requested with" 1260 " exclusive_storage") 1261 1262 # Call the External Storage's create script, 1263 # to provision a new Volume inside the External Storage 1264 _ExtStorageAction(constants.ES_ACTION_CREATE, unique_id, 1265 params, size=str(size), name=name, uuid=uuid) 1266 1267 return ExtStorageDevice(unique_id, children, size, params, dyn_params, 1268 *args)
1269
1270 - def Remove(self):
1271 """Remove the extstorage device. 1272 1273 """ 1274 if not self.minor and not self.Attach(): 1275 # The extstorage device doesn't exist. 1276 return 1277 1278 # First shutdown the device (remove mappings). 1279 self.Shutdown() 1280 1281 # Call the External Storage's remove script, 1282 # to remove the Volume from the External Storage 1283 _ExtStorageAction(constants.ES_ACTION_REMOVE, self.unique_id, 1284 self.ext_params, name=self.name, uuid=self.uuid)
1285
1286 - def Rename(self, new_id):
1287 """Rename this device. 1288 1289 """ 1290 pass
1291
1292 - def Attach(self):
1293 """Attach to an existing extstorage device. 1294 1295 This method maps the extstorage volume that matches our name with 1296 a corresponding block device and then attaches to this device. 1297 1298 """ 1299 self.attached = False 1300 1301 # Call the External Storage's attach script, 1302 # to attach an existing Volume to a block device under /dev 1303 self.dev_path = _ExtStorageAction(constants.ES_ACTION_ATTACH, 1304 self.unique_id, self.ext_params, 1305 name=self.name, uuid=self.uuid) 1306 1307 try: 1308 st = os.stat(self.dev_path) 1309 except OSError, err: 1310 logging.error("Error stat()'ing %s: %s", self.dev_path, str(err)) 1311 return False 1312 1313 if not stat.S_ISBLK(st.st_mode): 1314 logging.error("%s is not a block device", self.dev_path) 1315 return False 1316 1317 self.major = os.major(st.st_rdev) 1318 self.minor = utils.osminor(st.st_rdev) 1319 self.attached = True 1320 1321 return True
1322
1323 - def Assemble(self):
1324 """Assemble the device. 1325 1326 """ 1327 pass
1328
1329 - def Shutdown(self):
1330 """Shutdown the device. 1331 1332 """ 1333 if not self.minor and not self.Attach(): 1334 # The extstorage device doesn't exist. 1335 return 1336 1337 # Call the External Storage's detach script, 1338 # to detach an existing Volume from it's block device under /dev 1339 _ExtStorageAction(constants.ES_ACTION_DETACH, self.unique_id, 1340 self.ext_params, name=self.name, uuid=self.uuid) 1341 1342 self.minor = None 1343 self.dev_path = None
1344
1345 - def Open(self, force=False):
1346 """Make the device ready for I/O. 1347 1348 """ 1349 pass
1350
1351 - def Close(self):
1352 """Notifies that the device will no longer be used for I/O. 1353 1354 """ 1355 pass
1356
1357 - def Grow(self, amount, dryrun, backingstore, excl_stor):
1358 """Grow the Volume. 1359 1360 @type amount: integer 1361 @param amount: the amount (in mebibytes) to grow with 1362 @type dryrun: boolean 1363 @param dryrun: whether to execute the operation in simulation mode 1364 only, without actually increasing the size 1365 1366 """ 1367 if not backingstore: 1368 return 1369 if not self.Attach(): 1370 base.ThrowError("Can't attach to extstorage device during Grow()") 1371 1372 if dryrun: 1373 # we do not support dry runs of resize operations for now. 1374 return 1375 1376 new_size = self.size + amount 1377 1378 # Call the External Storage's grow script, 1379 # to grow an existing Volume inside the External Storage 1380 _ExtStorageAction(constants.ES_ACTION_GROW, self.unique_id, 1381 self.ext_params, size=str(self.size), grow=str(new_size), 1382 name=self.name, uuid=self.uuid)
1383
1384 - def SetInfo(self, text):
1385 """Update metadata with info text. 1386 1387 """ 1388 # Replace invalid characters 1389 text = re.sub("^[^A-Za-z0-9_+.]", "_", text) 1390 text = re.sub("[^-A-Za-z0-9_+.]", "_", text) 1391 1392 # Only up to 128 characters are allowed 1393 text = text[:128] 1394 1395 # Call the External Storage's setinfo script, 1396 # to set metadata for an existing Volume inside the External Storage 1397 _ExtStorageAction(constants.ES_ACTION_SETINFO, self.unique_id, 1398 self.ext_params, metadata=text, 1399 name=self.name, uuid=self.uuid)
1400
1401 1402 -def _ExtStorageAction(action, unique_id, ext_params, 1403 size=None, grow=None, metadata=None, 1404 name=None, uuid=None):
1405 """Take an External Storage action. 1406 1407 Take an External Storage action concerning or affecting 1408 a specific Volume inside the External Storage. 1409 1410 @type action: string 1411 @param action: which action to perform. One of: 1412 create / remove / grow / attach / detach 1413 @type unique_id: tuple (driver, vol_name) 1414 @param unique_id: a tuple containing the type of ExtStorage (driver) 1415 and the Volume name 1416 @type ext_params: dict 1417 @param ext_params: ExtStorage parameters 1418 @type size: integer 1419 @param size: the size of the Volume in mebibytes 1420 @type grow: integer 1421 @param grow: the new size in mebibytes (after grow) 1422 @type metadata: string 1423 @param metadata: metadata info of the Volume, for use by the provider 1424 @type name: string 1425 @param name: name of the Volume (objects.Disk.name) 1426 @type uuid: string 1427 @param uuid: uuid of the Volume (objects.Disk.uuid) 1428 @rtype: None or a block device path (during attach) 1429 1430 """ 1431 driver, vol_name = unique_id 1432 1433 # Create an External Storage instance of type `driver' 1434 status, inst_es = ExtStorageFromDisk(driver) 1435 if not status: 1436 base.ThrowError("%s" % inst_es) 1437 1438 # Create the basic environment for the driver's scripts 1439 create_env = _ExtStorageEnvironment(unique_id, ext_params, size, 1440 grow, metadata, name, uuid) 1441 1442 # Do not use log file for action `attach' as we need 1443 # to get the output from RunResult 1444 # TODO: find a way to have a log file for attach too 1445 logfile = None 1446 if action is not constants.ES_ACTION_ATTACH: 1447 logfile = _VolumeLogName(action, driver, vol_name) 1448 1449 # Make sure the given action results in a valid script 1450 if action not in constants.ES_SCRIPTS: 1451 base.ThrowError("Action '%s' doesn't result in a valid ExtStorage script" % 1452 action) 1453 1454 # Find out which external script to run according the given action 1455 script_name = action + "_script" 1456 script = getattr(inst_es, script_name) 1457 1458 # Run the external script 1459 result = utils.RunCmd([script], env=create_env, 1460 cwd=inst_es.path, output=logfile,) 1461 if result.failed: 1462 logging.error("External storage's %s command '%s' returned" 1463 " error: %s, logfile: %s, output: %s", 1464 action, result.cmd, result.fail_reason, 1465 logfile, result.output) 1466 1467 # If logfile is 'None' (during attach), it breaks TailFile 1468 # TODO: have a log file for attach too 1469 if action is not constants.ES_ACTION_ATTACH: 1470 lines = [utils.SafeEncode(val) 1471 for val in utils.TailFile(logfile, lines=20)] 1472 else: 1473 lines = result.output[-20:] 1474 1475 base.ThrowError("External storage's %s script failed (%s), last" 1476 " lines of output:\n%s", 1477 action, result.fail_reason, "\n".join(lines)) 1478 1479 if action == constants.ES_ACTION_ATTACH: 1480 return result.stdout
1481
1482 1483 -def ExtStorageFromDisk(name, base_dir=None):
1484 """Create an ExtStorage instance from disk. 1485 1486 This function will return an ExtStorage instance 1487 if the given name is a valid ExtStorage name. 1488 1489 @type base_dir: string 1490 @keyword base_dir: Base directory containing ExtStorage installations. 1491 Defaults to a search in all the ES_SEARCH_PATH dirs. 1492 @rtype: tuple 1493 @return: True and the ExtStorage instance if we find a valid one, or 1494 False and the diagnose message on error 1495 1496 """ 1497 if base_dir is None: 1498 es_base_dir = pathutils.ES_SEARCH_PATH 1499 else: 1500 es_base_dir = [base_dir] 1501 1502 es_dir = utils.FindFile(name, es_base_dir, os.path.isdir) 1503 1504 if es_dir is None: 1505 return False, ("Directory for External Storage Provider %s not" 1506 " found in search path" % name) 1507 1508 # ES Files dictionary, we will populate it with the absolute path 1509 # names; if the value is True, then it is a required file, otherwise 1510 # an optional one 1511 es_files = dict.fromkeys(constants.ES_SCRIPTS, True) 1512 1513 es_files[constants.ES_PARAMETERS_FILE] = True 1514 1515 for (filename, _) in es_files.items(): 1516 es_files[filename] = utils.PathJoin(es_dir, filename) 1517 1518 try: 1519 st = os.stat(es_files[filename]) 1520 except EnvironmentError, err: 1521 return False, ("File '%s' under path '%s' is missing (%s)" % 1522 (filename, es_dir, utils.ErrnoOrStr(err))) 1523 1524 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)): 1525 return False, ("File '%s' under path '%s' is not a regular file" % 1526 (filename, es_dir)) 1527 1528 if filename in constants.ES_SCRIPTS: 1529 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR: 1530 return False, ("File '%s' under path '%s' is not executable" % 1531 (filename, es_dir)) 1532 1533 parameters = [] 1534 if constants.ES_PARAMETERS_FILE in es_files: 1535 parameters_file = es_files[constants.ES_PARAMETERS_FILE] 1536 try: 1537 parameters = utils.ReadFile(parameters_file).splitlines() 1538 except EnvironmentError, err: 1539 return False, ("Error while reading the EXT parameters file at %s: %s" % 1540 (parameters_file, utils.ErrnoOrStr(err))) 1541 parameters = [v.split(None, 1) for v in parameters] 1542 1543 es_obj = \ 1544 objects.ExtStorage(name=name, path=es_dir, 1545 create_script=es_files[constants.ES_SCRIPT_CREATE], 1546 remove_script=es_files[constants.ES_SCRIPT_REMOVE], 1547 grow_script=es_files[constants.ES_SCRIPT_GROW], 1548 attach_script=es_files[constants.ES_SCRIPT_ATTACH], 1549 detach_script=es_files[constants.ES_SCRIPT_DETACH], 1550 setinfo_script=es_files[constants.ES_SCRIPT_SETINFO], 1551 verify_script=es_files[constants.ES_SCRIPT_VERIFY], 1552 supported_parameters=parameters) 1553 return True, es_obj
1554
1555 1556 -def _ExtStorageEnvironment(unique_id, ext_params, 1557 size=None, grow=None, metadata=None, 1558 name=None, uuid=None):
1559 """Calculate the environment for an External Storage script. 1560 1561 @type unique_id: tuple (driver, vol_name) 1562 @param unique_id: ExtStorage pool and name of the Volume 1563 @type ext_params: dict 1564 @param ext_params: the EXT parameters 1565 @type size: string 1566 @param size: size of the Volume (in mebibytes) 1567 @type grow: string 1568 @param grow: new size of Volume after grow (in mebibytes) 1569 @type metadata: string 1570 @param metadata: metadata info of the Volume 1571 @type name: string 1572 @param name: name of the Volume (objects.Disk.name) 1573 @type uuid: string 1574 @param uuid: uuid of the Volume (objects.Disk.uuid) 1575 @rtype: dict 1576 @return: dict of environment variables 1577 1578 """ 1579 vol_name = unique_id[1] 1580 1581 result = {} 1582 result["VOL_NAME"] = vol_name 1583 1584 # EXT params 1585 for pname, pvalue in ext_params.items(): 1586 result["EXTP_%s" % pname.upper()] = str(pvalue) 1587 1588 if size is not None: 1589 result["VOL_SIZE"] = size 1590 1591 if grow is not None: 1592 result["VOL_NEW_SIZE"] = grow 1593 1594 if metadata is not None: 1595 result["VOL_METADATA"] = metadata 1596 1597 if name is not None: 1598 result["VOL_CNAME"] = name 1599 1600 if uuid is not None: 1601 result["VOL_UUID"] = uuid 1602 1603 return result
1604
1605 1606 -def _VolumeLogName(kind, es_name, volume):
1607 """Compute the ExtStorage log filename for a given Volume and operation. 1608 1609 @type kind: string 1610 @param kind: the operation type (e.g. create, remove etc.) 1611 @type es_name: string 1612 @param es_name: the ExtStorage name 1613 @type volume: string 1614 @param volume: the name of the Volume inside the External Storage 1615 1616 """ 1617 # Check if the extstorage log dir is a valid dir 1618 if not os.path.isdir(pathutils.LOG_ES_DIR): 1619 base.ThrowError("Cannot find log directory: %s", pathutils.LOG_ES_DIR) 1620 1621 # TODO: Use tempfile.mkstemp to create unique filename 1622 basename = ("%s-%s-%s-%s.log" % 1623 (kind, es_name, volume, utils.TimestampForFilename())) 1624 return utils.PathJoin(pathutils.LOG_ES_DIR, basename)
1625
1626 1627 -def _VerifyDiskType(dev_type):
1628 if dev_type not in DEV_MAP: 1629 raise errors.ProgrammerError("Invalid block device type '%s'" % dev_type)
1630
1631 1632 -def _VerifyDiskParams(disk):
1633 """Verifies if all disk parameters are set. 1634 1635 """ 1636 missing = set(constants.DISK_LD_DEFAULTS[disk.dev_type]) - set(disk.params) 1637 if missing: 1638 raise errors.ProgrammerError("Block device is missing disk parameters: %s" % 1639 missing)
1640
1641 1642 -def FindDevice(disk, children):
1643 """Search for an existing, assembled device. 1644 1645 This will succeed only if the device exists and is assembled, but it 1646 does not do any actions in order to activate the device. 1647 1648 @type disk: L{objects.Disk} 1649 @param disk: the disk object to find 1650 @type children: list of L{bdev.BlockDev} 1651 @param children: the list of block devices that are children of the device 1652 represented by the disk parameter 1653 1654 """ 1655 _VerifyDiskType(disk.dev_type) 1656 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size, 1657 disk.params, disk.dynamic_params, 1658 disk.name, disk.uuid) 1659 if not device.attached: 1660 return None 1661 return device
1662
1663 1664 -def Assemble(disk, children):
1665 """Try to attach or assemble an existing device. 1666 1667 This will attach to assemble the device, as needed, to bring it 1668 fully up. It must be safe to run on already-assembled devices. 1669 1670 @type disk: L{objects.Disk} 1671 @param disk: the disk object to assemble 1672 @type children: list of L{bdev.BlockDev} 1673 @param children: the list of block devices that are children of the device 1674 represented by the disk parameter 1675 1676 """ 1677 _VerifyDiskType(disk.dev_type) 1678 _VerifyDiskParams(disk) 1679 device = DEV_MAP[disk.dev_type](disk.logical_id, children, disk.size, 1680 disk.params, disk.dynamic_params, 1681 disk.name, disk.uuid) 1682 device.Assemble() 1683 return device
1684
1685 1686 -def Create(disk, children, excl_stor):
1687 """Create a device. 1688 1689 @type disk: L{objects.Disk} 1690 @param disk: the disk object to create 1691 @type children: list of L{bdev.BlockDev} 1692 @param children: the list of block devices that are children of the device 1693 represented by the disk parameter 1694 @type excl_stor: boolean 1695 @param excl_stor: Whether exclusive_storage is active 1696 @rtype: L{bdev.BlockDev} 1697 @return: the created device, or C{None} in case of an error 1698 1699 """ 1700 _VerifyDiskType(disk.dev_type) 1701 _VerifyDiskParams(disk) 1702 device = DEV_MAP[disk.dev_type].Create(disk.logical_id, children, disk.size, 1703 disk.spindles, disk.params, excl_stor, 1704 disk.dynamic_params, 1705 disk.name, disk.uuid) 1706 return device
1707 1708 # Please keep this at the bottom of the file for visibility. 1709 DEV_MAP = { 1710 constants.DT_PLAIN: LogicalVolume, 1711 constants.DT_DRBD8: drbd.DRBD8Dev, 1712 constants.DT_BLOCK: PersistentBlockDevice, 1713 constants.DT_RBD: RADOSBlockDevice, 1714 constants.DT_EXT: ExtStorageDevice, 1715 constants.DT_FILE: FileStorage, 1716 constants.DT_SHARED_FILE: FileStorage, 1717 constants.DT_GLUSTER: GlusterStorage, 1718 } 1719 """Map disk types to disk type classes. 1720 1721 @see: L{Assemble}, L{FindDevice}, L{Create}.""" # pylint: disable=W0105 1722