yast/yast-storage-ng

View on GitHub
src/lib/y2partitioner/actions/controllers/btrfs_devices.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2019] 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 "yast"
require "y2storage"
require "y2partitioner/blk_device_restorer"
require "y2partitioner/actions/controllers/base"
require "y2partitioner/actions/controllers/available_devices"

module Y2Partitioner
  module Actions
    module Controllers
      # Controller class to deal with all stuff related to adding or removing devices to
      # a Btrfs filesystem
      class BtrfsDevices < Base
        include Yast::I18n

        include AvailableDevices

        # @return [Y2Storage::Filesystems::Btrfs]
        attr_reader :filesystem

        # @return [String]
        attr_reader :wizard_title

        # Constructor
        #
        # If the filesystem is not given, then a new one is created when the first device is selected.
        #
        # @param filesystem [Y2Storage::Filesystems::Btrfs]
        # @param wizard_title [String]
        def initialize(filesystem: nil, wizard_title: "")
          super()
          textdomain "storage"

          @filesystem = filesystem
          @wizard_title = wizard_title

          @metadata_raid_level = Y2Storage::BtrfsRaidLevel::DEFAULT
          @data_raid_level = Y2Storage::BtrfsRaidLevel::DEFAULT
        end

        # Metadata RAID level for the filesystem
        #
        # @return [Y2Storage::BtrfsRaidLevel]
        def metadata_raid_level
          raid_level(:metadata)
        end

        # Data RAID level for the filesystem
        #
        # @return [Y2Storage::BtrfsRaidLevel]
        def data_raid_level
          raid_level(:data)
        end

        # Sets the metadata RAID level for the filesystem
        #
        # @param value [Y2Storage::BtrfsRaidLevel]
        def metadata_raid_level=(value)
          save_raid_level(:metadata, value)
        end

        # Sets the data RAID level for the filesystem
        #
        # @param value [Y2Storage::BtrfsRaidLevel]
        def data_raid_level=(value)
          save_raid_level(:data, value)
        end

        # All possible RAID levels
        #
        # Btrfs requires a minimum number of devices for some RAID levels
        #
        # @return [Array<Y2Storage::BtrfsRaidLevel>]
        def raid_levels
          [
            Y2Storage::BtrfsRaidLevel::DEFAULT,
            Y2Storage::BtrfsRaidLevel::SINGLE,
            Y2Storage::BtrfsRaidLevel::DUP,
            Y2Storage::BtrfsRaidLevel::RAID0,
            Y2Storage::BtrfsRaidLevel::RAID1,
            Y2Storage::BtrfsRaidLevel::RAID10
          ]
        end

        # Allowed RAID levels depending on the selected devices
        #
        # @param data [:metadata, :data]
        # @return [Array<Y2Storage::BtrfsRaidLevel>]
        def allowed_raid_levels(data)
          raid_levels = [Y2Storage::BtrfsRaidLevel::DEFAULT]

          raid_levels += filesystem.send("allowed_#{data}_raid_levels")

          raid_levels - forbidden_raid_levels
        end

        # Devices that can be selected for being used by the Btrfs
        #
        # @see AvailableDevices
        #
        # @return [Array<Y2Storage::BlkDevice>]
        def available_devices
          super(current_graph) { |d| valid_device?(d) }
        end

        # Devices used by the Btrfs
        #
        # @return [Array<Y2Storage::BlkDevice>]
        def selected_devices
          return [] unless filesystem

          filesystem.plain_blk_devices.sort { |a, b| a.compare_by_name(b) }
        end

        # Adds a device to the Btrfs
        #
        # If the filesystem does not exist, a new one is created when adding the first device.
        #
        # Any previous children (like filesystems) are removed from the device (only encryption layer
        # is preserved).
        #
        # @param device [Y2Storage::BlkDevice]
        def add_device(device)
          # When the selected device is a partition, its partition id is set to linux.
          device.id = Y2Storage::PartitionId::LINUX if device.is?(:partition)

          device = device.encryption if device.encrypted?

          device.remove_descendants

          if filesystem.nil?
            create_filesystem(device)
          else
            filesystem.add_device(device)
          end
        end

        # Removes a device from the Btrfs
        #
        # @param device [Y2Storage::BlkDevice]
        def remove_device(device)
          device = device.encryption if device.encrypted?
          filesystem.remove_device(device)
          BlkDeviceRestorer.new(device.plain_device).restore_from_checkpoint
        end

        private

        # Helper method to get the RAID level (metadata or data)
        #
        # @param data [:metadata, :data]
        # @return [Y2Storage::BtrfsRaidLevel]
        def raid_level(data)
          # When the filesystem does not exist yet, the value is taken from the class attribute.
          # Otherwise, the filesystem value is taken.
          return instance_variable_get("@#{data}_raid_level") unless filesystem

          filesystem.send("#{data}_raid_level")
        end

        # Helper method to set the RAID level (metadata or data)
        #
        # @param data [:metadata, :data]
        # @param value [Y2Storage::BtrfsRaidLevel]
        def save_raid_level(data, value)
          if filesystem
            filesystem.send("#{data}_raid_level=", value)
          else
            instance_variable_set("@#{data}_raid_level", value)
          end
        end

        # Forbidden RAID levels
        #
        # RAID5 and RAID6 are not offered because Btrfs does not fully support them.
        #
        # @return [Array<Y2Storage::BtrfsRaidLevel>]
        def forbidden_raid_levels
          [Y2Storage::BtrfsRaidLevel::RAID5, Y2Storage::BtrfsRaidLevel::RAID6]
        end

        # Whether the available device is valid for being used by the Btrfs
        #
        # @param device [Y2Storage::BlkDevice]
        # @return [Boolean]
        def valid_device?(device)
          !device.is?(:encryption) && device.usable_as_blk_device? && !selected_device?(device)
        end

        # Whether the device is already selected for being used by the Btrfs
        #
        # @param device [Y2Storage::BlkDevice]
        # @return [Boolean]
        def selected_device?(device)
          selected_devices.include?(device)
        end

        # Creates a Btrfs filesystem over the given device
        #
        # @param device [Y2Storage::BlkDevice]
        # @return [Y2Storage::Filesystems::Btrfs]
        def create_filesystem(device)
          filesystem = device.create_filesystem(Y2Storage::Filesystems::Type::BTRFS)
          filesystem.metadata_raid_level = @metadata_raid_level
          filesystem.data_raid_level = @data_raid_level

          @filesystem = filesystem
        end
      end
    end
  end
end