bdev.py 58.1 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
#
Iustin Pop's avatar
Iustin Pop committed
2 3
#

Iustin Pop's avatar
Iustin Pop committed
4
# Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc.
Klaus Aehlig's avatar
Klaus Aehlig committed
5
# All rights reserved.
Iustin Pop's avatar
Iustin Pop committed
6
#
Klaus Aehlig's avatar
Klaus Aehlig committed
7 8 9
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
Iustin Pop's avatar
Iustin Pop committed
10
#
Klaus Aehlig's avatar
Klaus Aehlig committed
11 12
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
Iustin Pop's avatar
Iustin Pop committed
13
#
Klaus Aehlig's avatar
Klaus Aehlig committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Iustin Pop's avatar
Iustin Pop committed
29 30 31 32 33 34


"""Block device abstraction"""

import re
import errno
35
import stat
Manuel Franceschini's avatar
Manuel Franceschini committed
36
import os
37
import logging
38
import math
Iustin Pop's avatar
Iustin Pop committed
39 40 41

from ganeti import utils
from ganeti import errors
42
from ganeti import constants
43
from ganeti import objects
44
from ganeti import compat
45
from ganeti import pathutils
46
from ganeti import serializer
47
from ganeti.storage import base
48 49
from ganeti.storage import drbd
from ganeti.storage import filestorage
50 51


52 53 54 55 56 57 58
class RbdShowmappedJsonError(Exception):
  """`rbd showmmapped' JSON formatting error Exception class.

  """
  pass


Iustin Pop's avatar
Iustin Pop committed
59 60 61 62 63 64 65
def _CheckResult(result):
  """Throws an error if the given result is a failed one.

  @param result: result from RunCmd

  """
  if result.failed:
66 67
    base.ThrowError("Command: %s error: %s - %s",
                    result.cmd, result.fail_reason, result.output)
68 69


70
class LogicalVolume(base.BlockDev):
Iustin Pop's avatar
Iustin Pop committed
71 72 73
  """Logical Volume block device.

  """
74
  _VALID_NAME_RE = re.compile("^[a-zA-Z0-9+_.-]*$")
Michele Tartara's avatar
Michele Tartara committed
75
  _PARSE_PV_DEV_RE = re.compile(r"^([^ ()]+)\([0-9]+\)$")
76 77
  _INVALID_NAMES = compat.UniqueFrozenset([".", "..", "snapshot", "pvmove"])
  _INVALID_SUBSTRINGS = compat.UniqueFrozenset(["_mlog", "_mimage"])
78

79
  def __init__(self, unique_id, children, size, params, dyn_params):
Iustin Pop's avatar
Iustin Pop committed
80 81 82 83 84
    """Attaches to a LV device.

    The unique_id is a tuple (vg_name, lv_name)

    """
85 86
    super(LogicalVolume, self).__init__(unique_id, children, size, params,
                                        dyn_params)
Iustin Pop's avatar
Iustin Pop committed
87 88 89
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
      raise ValueError("Invalid configuration data %s" % str(unique_id))
    self._vg_name, self._lv_name = unique_id
90 91 92
    self._ValidateName(self._vg_name)
    self._ValidateName(self._lv_name)
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
Iustin Pop's avatar
Iustin Pop committed
93
    self._degraded = True
94
    self.major = self.minor = self.pe_size = self.stripe_count = None
95
    self.pv_names = None
Iustin Pop's avatar
Iustin Pop committed
96 97
    self.Attach()

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
  @staticmethod
  def _GetStdPvSize(pvs_info):
    """Return the the standard PV size (used with exclusive storage).

    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
    @rtype: float
    @return: size in MiB

    """
    assert len(pvs_info) > 0
    smallest = min([pv.size for pv in pvs_info])
    return smallest / (1 + constants.PART_MARGIN + constants.PART_RESERVED)

  @staticmethod
  def _ComputeNumPvs(size, pvs_info):
    """Compute the number of PVs needed for an LV (with exclusive storage).

    @type size: float
116
    @param size: LV size in MiB
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
    @param pvs_info: list of objects.LvmPvInfo, cannot be empty
    @rtype: integer
    @return: number of PVs needed
    """
    assert len(pvs_info) > 0
    pv_size = float(LogicalVolume._GetStdPvSize(pvs_info))
    return int(math.ceil(float(size) / pv_size))

  @staticmethod
  def _GetEmptyPvNames(pvs_info, max_pvs=None):
    """Return a list of empty PVs, by name.

    """
    empty_pvs = filter(objects.LvmPvInfo.IsEmpty, pvs_info)
    if max_pvs is not None:
      empty_pvs = empty_pvs[:max_pvs]
    return map((lambda pv: pv.name), empty_pvs)

Iustin Pop's avatar
Iustin Pop committed
135
  @classmethod
136 137
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
             dyn_params):
Iustin Pop's avatar
Iustin Pop committed
138 139 140 141
    """Create a new logical volume.

    """
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
Iustin Pop's avatar
Iustin Pop committed
142 143
      raise errors.ProgrammerError("Invalid configuration data %s" %
                                   str(unique_id))
Iustin Pop's avatar
Iustin Pop committed
144
    vg_name, lv_name = unique_id
145 146
    cls._ValidateName(vg_name)
    cls._ValidateName(lv_name)
147
    pvs_info = cls.GetPVInfo([vg_name])
Iustin Pop's avatar
Iustin Pop committed
148
    if not pvs_info:
149 150 151 152
      if excl_stor:
        msg = "No (empty) PVs found"
      else:
        msg = "Can't compute PV info for vg %s" % vg_name
153
      base.ThrowError(msg)
154
    pvs_info.sort(key=(lambda pv: pv.free), reverse=True)
155

156
    pvlist = [pv.name for pv in pvs_info]
157
    if compat.any(":" in v for v in pvlist):
158 159 160
      base.ThrowError("Some of your PVs have the invalid character ':' in their"
                      " name, this is not supported - please filter them out"
                      " in lvm.conf using either 'filter' or 'preferred_names'")
161

Iustin Pop's avatar
Iustin Pop committed
162
    current_pvs = len(pvlist)
163
    desired_stripes = params[constants.LDP_STRIPES]
164
    stripes = min(current_pvs, desired_stripes)
165 166

    if excl_stor:
167 168 169 170
      if spindles is None:
        base.ThrowError("Unspecified number of spindles: this is required"
                        "when exclusive storage is enabled, try running"
                        " gnt-cluster repair-disk-sizes")
171
      (err_msgs, _) = utils.LvmExclusiveCheckNodePvs(pvs_info)
172 173 174 175
      if err_msgs:
        for m in err_msgs:
          logging.warning(m)
      req_pvs = cls._ComputeNumPvs(size, pvs_info)
176 177 178 179 180 181
      if spindles < req_pvs:
        base.ThrowError("Requested number of spindles (%s) is not enough for"
                        " a disk of %d MB (at least %d spindles needed)",
                        spindles, size, req_pvs)
      else:
        req_pvs = spindles
182 183 184
      pvlist = cls._GetEmptyPvNames(pvs_info, req_pvs)
      current_pvs = len(pvlist)
      if current_pvs < req_pvs:
185 186 187
        base.ThrowError("Not enough empty PVs (spindles) to create a disk of %d"
                        " MB: %d available, %d needed",
                        size, current_pvs, req_pvs)
188
      assert current_pvs == len(pvlist)
189 190 191 192 193 194
      # We must update stripes to be sure to use all the desired spindles
      stripes = current_pvs
      if stripes > desired_stripes:
        # Don't warn when lowering stripes, as it's no surprise
        logging.warning("Using %s stripes instead of %s, to be able to use"
                        " %s spindles", stripes, desired_stripes, current_pvs)
195 196 197 198 199 200 201 202 203

    else:
      if stripes < desired_stripes:
        logging.warning("Could not use %d stripes for VG %s, as only %d PVs are"
                        " available.", desired_stripes, vg_name, current_pvs)
      free_size = sum([pv.free for pv in pvs_info])
      # The size constraint should have been checked from the master before
      # calling the create function.
      if free_size < size:
204 205
        base.ThrowError("Not enough free space: required %s,"
                        " available %s", size, free_size)
206

Iustin Pop's avatar
Iustin Pop committed
207 208 209 210
    # If the free space is not well distributed, we won't be able to
    # create an optimally-striped volume; in that case, we want to try
    # with N, N-1, ..., 2, and finally 1 (non-stripped) number of
    # stripes
211
    cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name]
Iustin Pop's avatar
Iustin Pop committed
212 213 214 215
    for stripes_arg in range(stripes, 0, -1):
      result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist)
      if not result.failed:
        break
Iustin Pop's avatar
Iustin Pop committed
216
    if result.failed:
217 218
      base.ThrowError("LV create failed (%s): %s",
                      result.fail_reason, result.output)
219
    return LogicalVolume(unique_id, children, size, params, dyn_params)
Iustin Pop's avatar
Iustin Pop committed
220 221

  @staticmethod
222
  def _GetVolumeInfo(lvm_cmd, fields):
Helga Velroyen's avatar
Helga Velroyen committed
223
    """Returns LVM Volume infos using lvm_cmd
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254

    @param lvm_cmd: Should be one of "pvs", "vgs" or "lvs"
    @param fields: Fields to return
    @return: A list of dicts each with the parsed fields

    """
    if not fields:
      raise errors.ProgrammerError("No fields specified")

    sep = "|"
    cmd = [lvm_cmd, "--noheadings", "--nosuffix", "--units=m", "--unbuffered",
           "--separator=%s" % sep, "-o%s" % ",".join(fields)]

    result = utils.RunCmd(cmd)
    if result.failed:
      raise errors.CommandError("Can't get the volume information: %s - %s" %
                                (result.fail_reason, result.output))

    data = []
    for line in result.stdout.splitlines():
      splitted_fields = line.strip().split(sep)

      if len(fields) != len(splitted_fields):
        raise errors.CommandError("Can't parse %s output: line '%s'" %
                                  (lvm_cmd, line))

      data.append(splitted_fields)

    return data

  @classmethod
255
  def GetPVInfo(cls, vg_names, filter_allocatable=True, include_lvs=False):
Iustin Pop's avatar
Iustin Pop committed
256 257
    """Get the free space info for PVs in a volume group.

258 259
    @param vg_names: list of volume group names, if empty all will be returned
    @param filter_allocatable: whether to skip over unallocatable PVs
260
    @param include_lvs: whether to include a list of LVs hosted on each PV
Iustin Pop's avatar
Iustin Pop committed
261

Iustin Pop's avatar
Iustin Pop committed
262
    @rtype: list
263
    @return: list of objects.LvmPvInfo objects
264

Iustin Pop's avatar
Iustin Pop committed
265
    """
266 267 268 269 270 271 272
    # We request "lv_name" field only if we care about LVs, so we don't get
    # a long list of entries with many duplicates unless we really have to.
    # The duplicate "pv_name" field will be ignored.
    if include_lvs:
      lvfield = "lv_name"
    else:
      lvfield = "pv_name"
273 274
    try:
      info = cls._GetVolumeInfo("pvs", ["pv_name", "vg_name", "pv_free",
275
                                        "pv_attr", "pv_size", lvfield])
276 277
    except errors.GenericError, err:
      logging.error("Can't get PV information: %s", err)
Iustin Pop's avatar
Iustin Pop committed
278
      return None
279

280 281 282 283 284
    # When asked for LVs, "pvs" may return multiple entries for the same PV-LV
    # pair. We sort entries by PV name and then LV name, so it's easy to weed
    # out duplicates.
    if include_lvs:
      info.sort(key=(lambda i: (i[0], i[5])))
Iustin Pop's avatar
Iustin Pop committed
285
    data = []
286 287
    lastpvi = None
    for (pv_name, vg_name, pv_free, pv_attr, pv_size, lv_name) in info:
288
      # (possibly) skip over pvs which are not allocatable
289
      if filter_allocatable and pv_attr[0] != "a":
Iustin Pop's avatar
Iustin Pop committed
290
        continue
291
      # (possibly) skip over pvs which are not in the right volume group(s)
292
      if vg_names and vg_name not in vg_names:
293
        continue
294 295 296 297 298 299 300 301 302 303 304 305 306 307
      # Beware of duplicates (check before inserting)
      if lastpvi and lastpvi.name == pv_name:
        if include_lvs and lv_name:
          if not lastpvi.lv_list or lastpvi.lv_list[-1] != lv_name:
            lastpvi.lv_list.append(lv_name)
      else:
        if include_lvs and lv_name:
          lvl = [lv_name]
        else:
          lvl = []
        lastpvi = objects.LvmPvInfo(name=pv_name, vg_name=vg_name,
                                    size=float(pv_size), free=float(pv_free),
                                    attributes=pv_attr, lv_list=lvl)
        data.append(lastpvi)
308 309 310

    return data

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
  @classmethod
  def _GetRawFreePvInfo(cls, vg_name):
    """Return info (size/free) about PVs.

    @type vg_name: string
    @param vg_name: VG name
    @rtype: tuple
    @return: (standard_pv_size_in_MiB, number_of_free_pvs, total_number_of_pvs)

    """
    pvs_info = cls.GetPVInfo([vg_name])
    if not pvs_info:
      pv_size = 0.0
      free_pvs = 0
      num_pvs = 0
    else:
      pv_size = cls._GetStdPvSize(pvs_info)
      free_pvs = len(cls._GetEmptyPvNames(pvs_info))
      num_pvs = len(pvs_info)
    return (pv_size, free_pvs, num_pvs)

332 333 334 335 336 337 338 339 340
  @classmethod
  def _GetExclusiveStorageVgFree(cls, vg_name):
    """Return the free disk space in the given VG, in exclusive storage mode.

    @type vg_name: string
    @param vg_name: VG name
    @rtype: float
    @return: free space in MiB
    """
341 342 343 344 345 346 347 348 349 350 351 352 353 354
    (pv_size, free_pvs, _) = cls._GetRawFreePvInfo(vg_name)
    return pv_size * free_pvs

  @classmethod
  def GetVgSpindlesInfo(cls, vg_name):
    """Get the free space info for specific VGs.

    @param vg_name: volume group name
    @rtype: tuple
    @return: (free_spindles, total_spindles)

    """
    (_, free_pvs, num_pvs) = cls._GetRawFreePvInfo(vg_name)
    return (free_pvs, num_pvs)
355

356
  @classmethod
357
  def GetVGInfo(cls, vg_names, excl_stor, filter_readonly=True):
358 359 360
    """Get the free space info for specific VGs.

    @param vg_names: list of volume group names, if empty all will be returned
361
    @param excl_stor: whether exclusive_storage is enabled
362 363 364
    @param filter_readonly: whether to skip over readonly VGs

    @rtype: list
365 366
    @return: list of tuples (free_space, total_size, name) with free_space in
             MiB
367 368 369

    """
    try:
370 371
      info = cls._GetVolumeInfo("vgs", ["vg_name", "vg_free", "vg_attr",
                                        "vg_size"])
372 373 374 375 376
    except errors.GenericError, err:
      logging.error("Can't get VG information: %s", err)
      return None

    data = []
377
    for vg_name, vg_free, vg_attr, vg_size in info:
378 379 380 381 382 383
      # (possibly) skip over vgs which are not writable
      if filter_readonly and vg_attr[0] == "r":
        continue
      # (possibly) skip over vgs which are not in the right volume group(s)
      if vg_names and vg_name not in vg_names:
        continue
384 385 386 387 388
      # Exclusive storage needs a different concept of free space
      if excl_stor:
        es_free = cls._GetExclusiveStorageVgFree(vg_name)
        assert es_free <= vg_free
        vg_free = es_free
389
      data.append((float(vg_free), float(vg_size), vg_name))
Iustin Pop's avatar
Iustin Pop committed
390 391 392

    return data

393 394 395 396 397 398 399 400 401 402 403
  @classmethod
  def _ValidateName(cls, name):
    """Validates that a given name is valid as VG or LV name.

    The list of valid characters and restricted names is taken out of
    the lvm(8) manpage, with the simplification that we enforce both
    VG and LV restrictions on the names.

    """
    if (not cls._VALID_NAME_RE.match(name) or
        name in cls._INVALID_NAMES or
404
        compat.any(substring in name for substring in cls._INVALID_SUBSTRINGS)):
405
      base.ThrowError("Invalid LVM name '%s'", name)
406

Iustin Pop's avatar
Iustin Pop committed
407 408 409 410 411 412
  def Remove(self):
    """Remove this logical volume.

    """
    if not self.minor and not self.Attach():
      # the LV does not exist
413
      return
Iustin Pop's avatar
Iustin Pop committed
414 415 416
    result = utils.RunCmd(["lvremove", "-f", "%s/%s" %
                           (self._vg_name, self._lv_name)])
    if result.failed:
417 418
      base.ThrowError("Can't lvremove: %s - %s",
                      result.fail_reason, result.output)
Iustin Pop's avatar
Iustin Pop committed
419

Iustin Pop's avatar
Iustin Pop committed
420 421 422 423 424 425 426 427 428 429 430 431 432
  def Rename(self, new_id):
    """Rename this logical volume.

    """
    if not isinstance(new_id, (tuple, list)) or len(new_id) != 2:
      raise errors.ProgrammerError("Invalid new logical id '%s'" % new_id)
    new_vg, new_name = new_id
    if new_vg != self._vg_name:
      raise errors.ProgrammerError("Can't move a logical volume across"
                                   " volume groups (from %s to to %s)" %
                                   (self._vg_name, new_vg))
    result = utils.RunCmd(["lvrename", new_vg, self._lv_name, new_name])
    if result.failed:
433
      base.ThrowError("Failed to rename the logical volume: %s", result.output)
434
    self._lv_name = new_name
435
    self.dev_path = utils.PathJoin("/dev", self._vg_name, self._lv_name)
436

437 438 439
  @classmethod
  def _ParseLvInfoLine(cls, line, sep):
    """Parse one line of the lvs output used in L{_GetLvInfo}.
Iustin Pop's avatar
Iustin Pop committed
440 441

    """
442
    elems = line.strip().rstrip(sep).split(sep)
443 444
    if len(elems) != 6:
      base.ThrowError("Can't parse LVS output, len(%s) != 6", str(elems))
Iustin Pop's avatar
Iustin Pop committed
445

446
    (status, major, minor, pe_size, stripes, pvs) = elems
447
    if len(status) < 6:
448
      base.ThrowError("lvs lv_attr is not at least 6 characters (%s)", status)
Iustin Pop's avatar
Iustin Pop committed
449 450 451 452

    try:
      major = int(major)
      minor = int(minor)
453
    except (TypeError, ValueError), err:
454
      base.ThrowError("lvs major/minor cannot be parsed: %s", str(err))
Iustin Pop's avatar
Iustin Pop committed
455

456 457 458
    try:
      pe_size = int(float(pe_size))
    except (TypeError, ValueError), err:
459
      base.ThrowError("Can't parse vg extent size: %s", err)
460 461 462 463

    try:
      stripes = int(stripes)
    except (TypeError, ValueError), err:
464 465
      base.ThrowError("Can't parse the number of stripes: %s", err)

466 467 468 469 470 471 472 473 474
    pv_names = []
    for pv in pvs.split(","):
      m = re.match(cls._PARSE_PV_DEV_RE, pv)
      if not m:
        base.ThrowError("Can't parse this device list: %s", pvs)
      pv_names.append(m.group(1))
    assert len(pv_names) > 0

    return (status, major, minor, pe_size, stripes, pv_names)
475 476 477 478 479 480

  @classmethod
  def _GetLvInfo(cls, dev_path, _run_cmd=utils.RunCmd):
    """Get info about the given existing LV to be used.

    """
481 482
    sep = "|"
    result = _run_cmd(["lvs", "--noheadings", "--separator=%s" % sep,
483 484
                       "--units=k", "--nosuffix",
                       "-olv_attr,lv_kernel_major,lv_kernel_minor,"
485
                       "vg_extent_size,stripes,devices", dev_path])
486 487 488 489 490 491 492 493 494 495 496 497
    if result.failed:
      base.ThrowError("Can't find LV %s: %s, %s",
                      dev_path, result.fail_reason, result.output)
    # the output can (and will) have multiple lines for multi-segment
    # LVs, as the 'stripes' parameter is a segment one, so we take
    # only the last entry, which is the one we're interested in; note
    # that with LVM2 anyway the 'stripes' value must be constant
    # across segments, so this is a no-op actually
    out = result.stdout.splitlines()
    if not out: # totally empty result? splitlines() returns at least
                # one line for any non-empty string
      base.ThrowError("Can't parse LVS output, no lines? Got '%s'", str(out))
498 499 500 501 502 503
    pv_names = set()
    for line in out:
      (status, major, minor, pe_size, stripes, more_pvs) = \
        cls._ParseLvInfoLine(line, sep)
      pv_names.update(more_pvs)
    return (status, major, minor, pe_size, stripes, pv_names)
504 505 506 507 508 509 510 511 512 513 514

  def Attach(self):
    """Attach to an existing LV.

    This method will try to see if an existing and active LV exists
    which matches our name. If so, its major/minor will be
    recorded.

    """
    self.attached = False
    try:
515
      (status, major, minor, pe_size, stripes, pv_names) = \
516 517
        self._GetLvInfo(self.dev_path)
    except errors.BlockDeviceError:
518 519
      return False

Iustin Pop's avatar
Iustin Pop committed
520 521
    self.major = major
    self.minor = minor
522 523
    self.pe_size = pe_size
    self.stripe_count = stripes
Iustin Pop's avatar
Iustin Pop committed
524
    self._degraded = status[0] == "v" # virtual volume, i.e. doesn't backing
Iustin Pop's avatar
Iustin Pop committed
525
                                      # storage
526
    self.pv_names = pv_names
527
    self.attached = True
Iustin Pop's avatar
Iustin Pop committed
528
    return True
Iustin Pop's avatar
Iustin Pop committed
529 530 531 532

  def Assemble(self):
    """Assemble the device.

Michael Hanselmann's avatar
Michael Hanselmann committed
533
    We always run `lvchange -ay` on the LV to ensure it's active before
534 535
    use, as there were cases when xenvg was not active after boot
    (also possibly after disk issues).
Iustin Pop's avatar
Iustin Pop committed
536 537

    """
538 539
    result = utils.RunCmd(["lvchange", "-ay", self.dev_path])
    if result.failed:
540
      base.ThrowError("Can't activate lv %s: %s", self.dev_path, result.output)
Iustin Pop's avatar
Iustin Pop committed
541 542 543 544 545 546 547 548

  def Shutdown(self):
    """Shutdown the device.

    This is a no-op for the LV device type, as we don't deactivate the
    volumes on shutdown.

    """
549
    pass
Iustin Pop's avatar
Iustin Pop committed
550

551 552 553 554 555 556 557 558
  def GetSyncStatus(self):
    """Returns the sync status of the device.

    If this device is a mirroring device, this function returns the
    status of the mirror.

    For logical volumes, sync_percent and estimated_time are always
    None (no recovery in progress, as we don't handle the mirrored LV
559 560
    case). The is_degraded parameter is the inverse of the ldisk
    parameter.
561

562 563 564 565 566
    For the ldisk parameter, we check if the logical volume has the
    'virtual' type, which means it's not backed by existing storage
    anymore (read from it return I/O error). This happens after a
    physical disk failure and subsequent 'vgreduce --removemissing' on
    the volume group.
567

Iustin Pop's avatar
Iustin Pop committed
568 569
    The status was already read in Attach, so we just return it.

570
    @rtype: objects.BlockDevStatus
Iustin Pop's avatar
Iustin Pop committed
571

572
    """
573 574 575 576 577
    if self._degraded:
      ldisk_status = constants.LDS_FAULTY
    else:
      ldisk_status = constants.LDS_OKAY

578 579 580 581 582 583
    return objects.BlockDevStatus(dev_path=self.dev_path,
                                  major=self.major,
                                  minor=self.minor,
                                  sync_percent=None,
                                  estimated_time=None,
                                  is_degraded=self._degraded,
584
                                  ldisk_status=ldisk_status)
585

Iustin Pop's avatar
Iustin Pop committed
586 587 588 589 590 591
  def Open(self, force=False):
    """Make the device ready for I/O.

    This is a no-op for the LV device type.

    """
592
    pass
Iustin Pop's avatar
Iustin Pop committed
593 594 595 596 597 598 599

  def Close(self):
    """Notifies that the device will no longer be used for I/O.

    This is a no-op for the LV device type.

    """
600
    pass
Iustin Pop's avatar
Iustin Pop committed
601 602 603 604

  def Snapshot(self, size):
    """Create a snapshot copy of an lvm block device.

Iustin Pop's avatar
Iustin Pop committed
605 606
    @returns: tuple (vg, lv)

Iustin Pop's avatar
Iustin Pop committed
607 608 609 610
    """
    snap_name = self._lv_name + ".snap"

    # remove existing snapshot if found
611 612
    snap = LogicalVolume((self._vg_name, snap_name), None, size, self.params,
                         self.dyn_params)
613
    base.IgnoreError(snap.Remove)
Iustin Pop's avatar
Iustin Pop committed
614

615
    vg_info = self.GetVGInfo([self._vg_name], False)
616
    if not vg_info:
617
      base.ThrowError("Can't compute VG info for vg %s", self._vg_name)
618
    free_size, _, _ = vg_info[0]
Iustin Pop's avatar
Iustin Pop committed
619
    if free_size < size:
620 621
      base.ThrowError("Not enough free space: required %s,"
                      " available %s", size, free_size)
Iustin Pop's avatar
Iustin Pop committed
622

Iustin Pop's avatar
Iustin Pop committed
623 624
    _CheckResult(utils.RunCmd(["lvcreate", "-L%dm" % size, "-s",
                               "-n%s" % snap_name, self.dev_path]))
Iustin Pop's avatar
Iustin Pop committed
625

Iustin Pop's avatar
Iustin Pop committed
626
    return (self._vg_name, snap_name)
Iustin Pop's avatar
Iustin Pop committed
627

628 629 630 631 632 633 634 635 636 637 638 639 640 641
  def _RemoveOldInfo(self):
    """Try to remove old tags from the lv.

    """
    result = utils.RunCmd(["lvs", "-o", "tags", "--noheadings", "--nosuffix",
                           self.dev_path])
    _CheckResult(result)

    raw_tags = result.stdout.strip()
    if raw_tags:
      for tag in raw_tags.split(","):
        _CheckResult(utils.RunCmd(["lvchange", "--deltag",
                                   tag.strip(), self.dev_path]))

642 643 644 645
  def SetInfo(self, text):
    """Update metadata with info text.

    """
646
    base.BlockDev.SetInfo(self, text)
647

648 649
    self._RemoveOldInfo()

650
    # Replace invalid characters
Iustin Pop's avatar
Iustin Pop committed
651 652
    text = re.sub("^[^A-Za-z0-9_+.]", "_", text)
    text = re.sub("[^-A-Za-z0-9_+.]", "_", text)
653 654 655 656

    # Only up to 128 characters are allowed
    text = text[:128]

Iustin Pop's avatar
Iustin Pop committed
657
    _CheckResult(utils.RunCmd(["lvchange", "--addtag", text, self.dev_path]))
658

659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
  def _GetGrowthAvaliabilityExclStor(self):
    """Return how much the disk can grow with exclusive storage.

    @rtype: float
    @return: available space in Mib

    """
    pvs_info = self.GetPVInfo([self._vg_name])
    if not pvs_info:
      base.ThrowError("Cannot get information about PVs for %s", self.dev_path)
    std_pv_size = self._GetStdPvSize(pvs_info)
    free_space = sum(pvi.free - (pvi.size - std_pv_size)
                        for pvi in pvs_info
                        if pvi.name in self.pv_names)
    return free_space

675
  def Grow(self, amount, dryrun, backingstore, excl_stor):
676 677 678
    """Grow the logical volume.

    """
Iustin Pop's avatar
Iustin Pop committed
679 680
    if not backingstore:
      return
681 682
    if self.pe_size is None or self.stripe_count is None:
      if not self.Attach():
683
        base.ThrowError("Can't attach to LV during Grow()")
684
    full_stripe_size = self.pe_size * self.stripe_count
685 686
    # pe_size is in KB
    amount *= 1024
687 688 689
    rest = amount % full_stripe_size
    if rest != 0:
      amount += full_stripe_size - rest
690
    cmd = ["lvextend", "-L", "+%dk" % amount]
691 692
    if dryrun:
      cmd.append("--test")
693
    if excl_stor:
694 695 696 697 698 699
      free_space = self._GetGrowthAvaliabilityExclStor()
      # amount is in KiB, free_space in MiB
      if amount > free_space * 1024:
        base.ThrowError("Not enough free space to grow %s: %d MiB required,"
                        " %d available", self.dev_path, amount / 1024,
                        free_space)
700 701 702 703 704
      # Disk growth doesn't grow the number of spindles, so we must stay within
      # our assigned volumes
      pvlist = list(self.pv_names)
    else:
      pvlist = []
705 706 707 708 709
    # we try multiple algorithms since the 'best' ones might not have
    # space available in the right place, but later ones might (since
    # they have less constraints); also note that only recent LVM
    # supports 'cling'
    for alloc_policy in "contiguous", "cling", "normal":
710 711
      result = utils.RunCmd(cmd + ["--alloc", alloc_policy, self.dev_path] +
                            pvlist)
712 713
      if not result.failed:
        return
714
    base.ThrowError("Can't grow LV %s: %s", self.dev_path, result.output)
Iustin Pop's avatar
Iustin Pop committed
715

716 717 718 719 720 721 722
  def GetActualSpindles(self):
    """Return the number of spindles used.

    """
    assert self.attached, "BlockDevice not attached in GetActualSpindles()"
    return len(self.pv_names)

723

724
class FileStorage(base.BlockDev):
Manuel Franceschini's avatar
Manuel Franceschini committed
725
  """File device.
726

727
  This class represents a file storage backend device.
Manuel Franceschini's avatar
Manuel Franceschini committed
728 729

  The unique_id for the file device is a (file_driver, file_path) tuple.
730

Manuel Franceschini's avatar
Manuel Franceschini committed
731
  """
732
  def __init__(self, unique_id, children, size, params, dyn_params):
Manuel Franceschini's avatar
Manuel Franceschini committed
733 734 735 736 737
    """Initalizes a file device backend.

    """
    if children:
      raise errors.BlockDeviceError("Invalid setup for file device")
738 739
    super(FileStorage, self).__init__(unique_id, children, size, params,
                                      dyn_params)
Manuel Franceschini's avatar
Manuel Franceschini committed
740 741 742 743
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
      raise ValueError("Invalid configuration data %s" % str(unique_id))
    self.driver = unique_id[0]
    self.dev_path = unique_id[1]
744

745
    filestorage.CheckFileStoragePathAcceptance(self.dev_path)
746

Iustin Pop's avatar
Iustin Pop committed
747
    self.Attach()
Manuel Franceschini's avatar
Manuel Franceschini committed
748 749 750 751 752 753 754 755

  def Assemble(self):
    """Assemble the device.

    Checks whether the file device exists, raises BlockDeviceError otherwise.

    """
    if not os.path.exists(self.dev_path):
756
      base.ThrowError("File device '%s' does not exist" % self.dev_path)
Manuel Franceschini's avatar
Manuel Franceschini committed
757 758 759 760

  def Shutdown(self):
    """Shutdown the device.

Michael Hanselmann's avatar
Michael Hanselmann committed
761
    This is a no-op for the file type, as we don't deactivate
Manuel Franceschini's avatar
Manuel Franceschini committed
762 763 764
    the file on shutdown.

    """
765
    pass
Manuel Franceschini's avatar
Manuel Franceschini committed
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785

  def Open(self, force=False):
    """Make the device ready for I/O.

    This is a no-op for the file type.

    """
    pass

  def Close(self):
    """Notifies that the device will no longer be used for I/O.

    This is a no-op for the file type.

    """
    pass

  def Remove(self):
    """Remove the file backing the block device.

Iustin Pop's avatar
Iustin Pop committed
786 787
    @rtype: boolean
    @return: True if the removal was successful
Manuel Franceschini's avatar
Manuel Franceschini committed
788 789 790 791 792

    """
    try:
      os.remove(self.dev_path)
    except OSError, err:
793
      if err.errno != errno.ENOENT:
794
        base.ThrowError("Can't remove file '%s': %s", self.dev_path, err)
Manuel Franceschini's avatar
Manuel Franceschini committed
795

796 797 798 799 800
  def Rename(self, new_id):
    """Renames the file.

    """
    # TODO: implement rename for file-based storage
801
    base.ThrowError("Rename is not supported for file-based storage")
802

803
  def Grow(self, amount, dryrun, backingstore, excl_stor):
804 805 806 807 808
    """Grow the file

    @param amount: the amount (in mebibytes) to grow with

    """
Iustin Pop's avatar
Iustin Pop committed
809 810
    if not backingstore:
      return
811 812 813 814 815
    # Check that the file exists
    self.Assemble()
    current_size = self.GetActualSize()
    new_size = current_size + amount * 1024 * 1024
    assert new_size > current_size, "Cannot Grow with a negative amount"
816 817 818
    # We can't really simulate the growth
    if dryrun:
      return
819 820 821 822 823
    try:
      f = open(self.dev_path, "a+")
      f.truncate(new_size)
      f.close()
    except EnvironmentError, err:
824
      base.ThrowError("Error in file growth: %", str(err))
825

Manuel Franceschini's avatar
Manuel Franceschini committed
826 827 828 829 830
  def Attach(self):
    """Attach to an existing file.

    Check if this file already exists.

Iustin Pop's avatar
Iustin Pop committed
831 832
    @rtype: boolean
    @return: True if file exists
Manuel Franceschini's avatar
Manuel Franceschini committed
833 834

    """
Iustin Pop's avatar
Iustin Pop committed
835 836
    self.attached = os.path.exists(self.dev_path)
    return self.attached
Manuel Franceschini's avatar
Manuel Franceschini committed
837

838 839 840 841 842 843 844 845 846 847 848
  def GetActualSize(self):
    """Return the actual disk size.

    @note: the device needs to be active when this is called

    """
    assert self.attached, "BlockDevice not attached in GetActualSize()"
    try:
      st = os.stat(self.dev_path)
      return st.st_size
    except OSError, err:
849
      base.ThrowError("Can't stat %s: %s", self.dev_path, err)
850

Manuel Franceschini's avatar
Manuel Franceschini committed
851
  @classmethod
852 853
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
             dyn_params):
Manuel Franceschini's avatar
Manuel Franceschini committed
854 855
    """Create a new file.

Iustin Pop's avatar
Iustin Pop committed
856
    @param size: the size of file in MiB
Manuel Franceschini's avatar
Manuel Franceschini committed
857

Iustin Pop's avatar
Iustin Pop committed
858 859
    @rtype: L{bdev.FileStorage}
    @return: an instance of FileStorage
Manuel Franceschini's avatar
Manuel Franceschini committed
860 861

    """
862 863 864
    if excl_stor:
      raise errors.ProgrammerError("FileStorage device requested with"
                                   " exclusive_storage")
Manuel Franceschini's avatar
Manuel Franceschini committed
865 866
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
      raise ValueError("Invalid configuration data %s" % str(unique_id))
867

Manuel Franceschini's avatar
Manuel Franceschini committed
868
    dev_path = unique_id[1]
869

870
    filestorage.CheckFileStoragePathAcceptance(dev_path)
871

Manuel Franceschini's avatar
Manuel Franceschini committed
872
    try:
873 874
      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
      f = os.fdopen(fd, "w")
Manuel Franceschini's avatar
Manuel Franceschini committed
875 876
      f.truncate(size * 1024 * 1024)
      f.close()
877 878
    except EnvironmentError, err:
      if err.errno == errno.EEXIST:
879 880
        base.ThrowError("File already existing: %s", dev_path)
      base.ThrowError("Error in file creation: %", str(err))
Manuel Franceschini's avatar
Manuel Franceschini committed
881

882
    return FileStorage(unique_id, children, size, params, dyn_params)
Manuel Franceschini's avatar
Manuel Franceschini committed
883 884


885
class PersistentBlockDevice(base.BlockDev):
886 887 888 889 890 891 892 893 894
  """A block device with persistent node

  May be either directly attached, or exposed through DM (e.g. dm-multipath).
  udev helpers are probably required to give persistent, human-friendly
  names.

  For the time being, pathnames are required to lie under /dev.

  """
895
  def __init__(self, unique_id, children, size, params, dyn_params):
896 897 898 899 900
    """Attaches to a static block device.

    The unique_id is a path under /dev.

    """
901
    super(PersistentBlockDevice, self).__init__(unique_id, children, size,
902
                                                params, dyn_params)
903 904 905
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
      raise ValueError("Invalid configuration data %s" % str(unique_id))
    self.dev_path = unique_id[1]
Iustin Pop's avatar
Iustin Pop committed
906
    if not os.path.realpath(self.dev_path).startswith("/dev/"):
907 908 909 910 911 912 913 914 915 916 917 918 919 920
      raise ValueError("Full path '%s' lies outside /dev" %
                              os.path.realpath(self.dev_path))
    # TODO: this is just a safety guard checking that we only deal with devices
    # we know how to handle. In the future this will be integrated with
    # external storage backends and possible values will probably be collected
    # from the cluster configuration.
    if unique_id[0] != constants.BLOCKDEV_DRIVER_MANUAL:
      raise ValueError("Got persistent block device of invalid type: %s" %
                       unique_id[0])

    self.major = self.minor = None
    self.Attach()

  @classmethod
921 922
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
             dyn_params):
923 924 925 926 927
    """Create a new device

    This is a noop, we only return a PersistentBlockDevice instance

    """
928 929 930
    if excl_stor:
      raise errors.ProgrammerError("Persistent block device requested with"
                                   " exclusive_storage")
931
    return PersistentBlockDevice(unique_id, children, 0, params, dyn_params)
932 933 934 935 936 937 938 939 940 941 942 943 944

  def Remove(self):
    """Remove a device

    This is a noop

    """
    pass

  def Rename(self, new_id):
    """Rename this device.

    """
945
    base.ThrowError("Rename is not supported for PersistentBlockDev storage")
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963

  def Attach(self):
    """Attach to an existing block device.


    """
    self.attached = False
    try:
      st = os.stat(self.dev_path)
    except OSError, err:
      logging.error("Error stat()'ing %s: %s", self.dev_path, str(err))
      return False

    if not stat.S_ISBLK(st.st_mode):
      logging.error("%s is not a block device", self.dev_path)
      return False

    self.major = os.major(st.st_rdev)
Klaus Aehlig's avatar
Klaus Aehlig committed
964
    self.minor = utils.osminor(st.st_rdev)
965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992
    self.attached = True

    return True

  def Assemble(self):
    """Assemble the device.

    """
    pass

  def Shutdown(self):
    """Shutdown the device.

    """
    pass

  def Open(self, force=False):
    """Make the device ready for I/O.

    """
    pass

  def Close(self):
    """Notifies that the device will no longer be used for I/O.

    """
    pass

993
  def Grow(self, amount, dryrun, backingstore, excl_stor):
994 995 996
    """Grow the logical volume.

    """
997
    base.ThrowError("Grow is not supported for PersistentBlockDev storage")
998 999


1000
class RADOSBlockDevice(base.BlockDev):
1001 1002 1003 1004 1005 1006 1007
  """A RADOS Block Device (rbd).

  This class implements the RADOS Block Device for the backend. You need
  the rbd kernel driver, the RADOS Tools and a working RADOS cluster for
  this to be functional.

  """
1008
  def __init__(self, unique_id, children, size, params, dyn_params):
1009 1010 1011
    """Attaches to an rbd device.

    """