yast/yast-storage-ng

View on GitHub
src/lib/y2storage/proposal/settings_generator.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2018-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/proposal/settings_adjustment"

module Y2Storage
  module Proposal
    # Class for a settings generator used to perform the initial proposal
    #
    # @see InitialGuidedProposal
    class SettingsGenerator
      include Yast::Logger

      # Constructor
      #
      # A copy of the given settings is internally stored.
      #
      # @param settings [ProposalSettings] base settings
      def initialize(settings)
        @settings = settings.deep_copy
        @adjustments = SettingsAdjustment.new
      end

      # This attribute contains information about any adjustment done to the settings
      #
      # @return [Proposal::SettingsAdjustment]
      attr_reader :adjustments

      # Next settings generated from the current version of the settings. The new settings are
      # generated by disabling some of its properties. A copy of the stored {#settings} is returned.
      #
      # @see #calculate_next_settings
      #
      # @return [ProposalSettings, nil] nil if nothing else can be disabled in the current settings
      def next_settings
        initial_settings || calculate_next_settings
      end

      private

      # Next settings to use for a new attempt of the {InitialGuidedProposal}
      #
      # It tries to disable any property from the first configurable volume: adjust by ram,
      # snapshots or the volume itself.
      #
      # @see #next_settings
      #
      # @return [ProposalSettings, nil] nil if nothing else can be disabled in the current settings
      def calculate_next_settings
        volume = first_configurable_volume

        return nil if volume.nil?

        settings_after_disabling_adjust_by_ram(volume) ||
          settings_after_disabling_snapshots(volume) ||
          settings_after_disabling_volume(volume)
      end

      # Current settings
      #
      # This settings are used to calculate the next settings
      #
      # @return [ProposalSettings]
      attr_reader :settings

      # Settings to use for first time
      #
      # The first time that {#next_settings} is called, a copy of the original settings
      # is returned.
      #
      # @return [ProposalSettings]
      def initial_settings
        return nil if @used_initial_settings

        @used_initial_settings = true

        copy_settings
      end

      # Copies current settings
      #
      # @return [ProposalSettings]
      def copy_settings
        settings.deep_copy
      end

      # Returns the first volume (according to disable_order) whose settings could be modified.
      #
      # @note Volumes without disable order are never altered.
      #
      # @return [VolumeSpecification, nil]
      def first_configurable_volume
        settings.volumes.select { |v| configurable_volume?(v) }.min_by(&:disable_order)
      end

      # Copy of the current settings after disabling adjust by ram property from the
      # given volume specification.
      #
      # @param volume [VolumeSpecification]
      # @return [ProposalSettings, nil] nil if adjust by ram is already disabled
      def settings_after_disabling_adjust_by_ram(volume)
        return nil unless adjust_by_ram_active_and_configurable?(volume)

        disable_volume_property(volume, :adjust_by_ram)

        copy_settings
      end

      # Copy of the current settings after disabling snapshots property from the
      # given volume specification.
      #
      # @param volume [VolumeSpecification]
      # @return [ProposalSettings, nil] nil if snapshots is already disabled
      def settings_after_disabling_snapshots(volume)
        return nil unless snapshots_active_and_configurable?(volume)

        disable_volume_property(volume, :snapshots)

        copy_settings
      end

      # Copy of the current settings after disabling the given volume specification
      #
      # @param volume [VolumeSpecification]
      # @return [ProposalSettings, nil] nil if the volume is already disabled
      def settings_after_disabling_volume(volume)
        return nil unless proposed_active_and_configurable?(volume)

        disable_volume_property(volume, :proposed)

        copy_settings
      end

      def disable_volume_property(volume, property)
        log.info("Disabling '#{property}' for '#{volume.mount_point}'")

        volume.public_send(:"#{property}=", false)
        @adjustments = adjustments.add_volume_attr(volume, property, false)
      end

      # A volume is configurable if it is proposed and its settings can be modified.
      # That is, #adjust_by_ram, #snapshots or #proposed are true and some of them
      # could be change to false.
      #
      # @note A volume is considered configurable if it has disable order. Otherwise,
      #   only the user is allowed to manually disable the volume or any of its features
      #   (e.g. snapshots) using the UI.
      #
      # @param volume [VolumeSpecification]
      # @return [Boolean]
      def configurable_volume?(volume)
        volume.proposed? && !volume.disable_order.nil? && (
          proposed_active_and_configurable?(volume) ||
          adjust_by_ram_active_and_configurable?(volume) ||
          snapshots_active_and_configurable?(volume))
      end

      # Whether the volume is proposed and it could be configured
      #
      # @param volume [VolumeSpecification]
      # @return [Boolean]
      def proposed_active_and_configurable?(volume)
        active_and_configurable?(volume, :proposed)
      end

      # Whether the volume has adjust_by_ram to true and it could be configured
      #
      # @param volume [VolumeSpecification]
      # @return [Boolean]
      def adjust_by_ram_active_and_configurable?(volume)
        active_and_configurable?(volume, :adjust_by_ram)
      end

      # Whether the volume has snapshots to true and it could be configured
      #
      # @param volume [VolumeSpecification]
      # @return [Boolean]
      def snapshots_active_and_configurable?(volume)
        return false unless volume.fs_type.is?(:btrfs)

        active_and_configurable?(volume, :snapshots)
      end

      # Whether a volume has an attribute to true and it could be configured
      #
      # @param volume [VolumeSpecification]
      # @param attr [String, Symbol]
      #
      # @return [Boolean]
      def active_and_configurable?(volume, attr)
        volume.send(attr) && volume.send("#{attr}_configurable")
      end
    end
  end
end