yast/yast-storage-ng

View on GitHub
src/lib/y2storage/lvm_lv.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2017-2021] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "y2storage/storage_class_wrapper"
require "y2storage/blk_device"

module Y2Storage
  # A logical volume of the Logical Volume Manager (LVM)
  #
  # This is a wrapper for Storage::LvmLv
  class LvmLv < BlkDevice
    wrap_class Storage::LvmLv

    # @!method lv_name
    #   @return [String] logical volume name (e.g. "lv1"), not to be confused
    #     with BlkDevice#name (e.g. "/dev/mapper/vg0/lv1")
    storage_forward :lv_name

    # @see BlkDevice#stable_name?
    #
    # The name is based on {#lv_name} and {LvmVg#vg_name}. Since both are
    # stable, the name should not change across reboots.
    #
    # @return [Boolean]
    def stable_name?
      true
    end

    # @!method lvm_vg
    #   @return [LvmVg] volume group the LV belongs to
    storage_forward :lvm_vg, as: "LvmVg"

    # @see #thin_pool
    storage_forward :storage_thin_pool, to: :thin_pool, as: "LvmLv"
    private :storage_thin_pool

    # @!method lv_type
    #   @return [LvType] type of the logical volume
    storage_forward :lv_type, as: "LvType"

    # @see #stripes
    storage_forward :storage_stripes, to: :stripes
    private :storage_stripes

    # @!method stripes=(num_stripes)
    #   Sets the number of stripes. The size of the LV must be a multiple of
    #   the number of stripes and the stripe size. Thin LVs cannot be striped.
    #
    #   @param num_stripes [Integer]
    #   @raise [Storage::Exception] if the number of stripes is invalid
    #     (i.e., bigger than 128)
    storage_forward :stripes=

    # @see #stripe_size
    storage_forward :storage_stripe_size, to: :stripe_size, as: "DiskSize"
    private :storage_stripe_size

    # @!method stripe_size=(stripe_size)
    #   Sets the size of a stripe
    #
    #   @param stripe_size [DiskSize, Integer]
    storage_forward :stripe_size=

    # @!method max_size_for_lvm_lv(lv_type)
    #   Returns the max size for a new logical volume of type lv_type. The size
    #   may be limited by other parameters (e.g. the filesystem on it).
    #
    #   The max size for thin logical volumes is in general theoretic (max size
    #   that can be represented)
    #
    #   @param lv_type [LvType]
    #   @return [DiskSize]
    storage_forward :max_size_for_lvm_lv, as: "DiskSize"

    # @!method lvm_lvs
    #   Returns the thin volumes over a thin pool, so it only makes sense to be
    #   called over a thin pool volume. For thin and normal logical volumes it
    #   returns an empty list.
    #
    #   @return [Array<LvmLv>] logical volumes in the thin pool, in no particular order
    storage_forward :lvm_lvs, as: "LvmLv"

    # @!method snapshots
    #   Returns the snapshots of a logical volume, if any.
    #
    #   @return [Array<LvmLv>] a collection of snapshots volumes
    storage_forward :snapshots, as: "LvmLv"

    # @!method origin
    #   Returns the original volume of an snapshot.
    #
    #   @return [LvmLv] the original logical volume
    storage_forward :origin, check_with: :has_origin, as: "LvmLv"

    # @!method create_lvm_lv(lv_name, lv_type, size)
    #   Creates a logical volume with name lv_name and type lv_type in the thin pool.
    #   Only supported lv_type is THIN.
    #
    #   @param lv_name [String] name of the new volume (see {LvmLv#lv_name})
    #   @param lv_type [LvType] type of the new volume
    #   @param size [DiskSize] size of the new volume
    #
    #   @raise [Storage::Exception]
    #
    #   @return [LvmLv]
    storage_forward :create_lvm_lv, as: "LvmLv"

    # @!method self.all(devicegraph)
    #   @param devicegraph [Devicegraph]
    #   @return [Array<Disk>] all the logical volumes in the given devicegraph
    storage_class_forward :all, as: "LvmLv"

    # Returns the thin pool holding a thin volume
    #
    # @return [LvmLv] the thin pool when dealing with a thin LV; nil otherwise
    def thin_pool
      lv_type.is?(:thin) ? storage_thin_pool : nil
    end

    # Number of stripes
    #
    # @note it returns the value of Storage::LvmLv#stripes, except for thin volumes that are going
    #   to report the striping defined for their thin pools.
    #
    # @return [Integer] 0 when the LV is not striped; Storage::LvmLv#stripes otherwise
    def stripes
      thin_pool ? thin_pool.stripes : storage_stripes
    end

    # Size of a stripe
    #
    # @note it returns the value of Storage::LvmLv#stripe_size, except for thin volumes that are
    #   going to report the striping size defined for their thin pools.
    #
    # @return [DiskSize]
    def stripe_size
      thin_pool ? thin_pool.stripe_size : storage_stripe_size
    end

    # Whether the logical volume is striped
    #
    # @return [Boolean]
    def striped?
      stripes > 1
    end

    # Whether the thin pool is overcommitted
    #
    # @note Overcommitting means that a thin pool has not enough size to cover
    #   all thin volumes created over the thin pool. In consequence, this method
    #   only makes sense for thin pools. For other logical volumes it always
    #   returns false.
    #
    # @return [Boolean] true if the thin pool is overcommitted; false otherwise.
    def overcommitted?
      return false unless lv_type.is?(:thin_pool)

      size < DiskSize.sum(lvm_lvs.map(&:size))
    end

    # Resizes the volume, taking resizing limits, extent size and stripes into account.
    #
    # It does nothing if resizing is not possible (see {ResizeInfo#resize_ok?}).
    # Otherwise, it sets the size of the LV based on the requested size.
    #
    # If the requested size is out of the min/max limits provided by
    # {#resize_info}, the end will be adjusted to the corresponding limit.
    #
    # If the requested size is between the limits, the size will be set to the
    # closest valid (i.e. divisible by the extent size) value, rounding down if
    # needed.
    #
    # The resulting size is also rounded down according to the number of stripes. In
    # case of setting the minimum possible size, this method does not round the size
    # down in order to avoid sizes below the minimum. Anyway, lvcreate command always
    # tries to round the number of extents up. So, even though the minimum size is not
    # rounded here, it will be correctly rounded by lvcreate.
    #
    # @param new_size [DiskSize] tentative new size of the volume, take into
    #   account that the result may be slightly smaller after rounding it down
    #   based on the extent size
    def resize(new_size)
      log.info "Trying to resize #{name} (#{size}) to #{new_size}"
      return unless can_resize?

      # The sizes in resize_info are already rounded to the extent size
      self.size =
        if new_size > resize_info.max_size
          resize_info.max_size
        elsif new_size < resize_info.min_size
          resize_info.min_size
        else
          new_size
        end

      self.size = rounded_size if size != resize_info.min_size

      log.info "Size of #{name} set to #{size}"
    end

    # Rounded-down size according to the extent size and the number of stripes
    #
    # When a logical volume is created, libstorage-ng calculates the number of extents for the new
    # logical volume. As result, the size is rounded-down to the extent size. But then, lvcreate command
    # rounds up the number of extents to make it multiple of the number of stripes. This could lead to a
    # number of extents that exceeds the total number of extents from the volume group.
    #
    # @return [DiskSize]
    def rounded_size
      return size if size.zero? || !striped?

      extends = size.to_i / lvm_vg.extent_size.to_i
      extends -= (extends % stripes)

      lvm_vg.extent_size * extends
    end

    protected

    # @see Device#is?
    def types_for_is
      types = super
      types << :lvm_lv
      types << :lvm_snapshot if origin
      types << :lvm_thin_snapshot if lv_type.is?(:thin) && origin
      types << "lvm_#{lv_type}".to_sym unless lv_type.is?(:unknown, :normal, :snapshot)
      types
    end
  end
end