yast/yast-storage-ng

View on GitHub
src/lib/y2storage/proposal/space_maker_actions/bigger_resize_strategy.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Copyright (c) [2023-2024] 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/space_maker_actions/shrink"
require "y2storage/proposal/space_maker_actions/delete"
require "y2storage/proposal/space_maker_actions/wipe"

module Y2Storage
  module Proposal
    module SpaceMakerActions
      # Strategy for {SpaceMakerActions::List} used for the case in which the actions to perform are
      # explicitly configured as part of the proposal settings.
      class BiggerResizeStrategy
        # Constructor
        #
        # @param settings [ProposalSpaceSettings] proposal settings
        def initialize(settings, _disk_analyzer)
          @settings = settings
          @to_delete_mandatory = []
          @to_delete_optional = []
          @to_wipe = []
          @to_resize = []
        end

        # @param disk [Disk] see {List}
        def add_mandatory_actions(disk)
          return unless disk.partition_table?

          devices = partitions(disk).select { |p| configured?(p, :force_delete) }
          to_delete_mandatory.concat(devices)
        end

        # @param disk [Disk] see {List}
        def add_optional_actions(disk, _lvm_helper)
          add_wipe(disk)
          add_resize(disk)
          add_optional_delete(disk)
        end

        # @return [Action, nil] nil if there are no more actions in the list
        def next
          source = source_for_next
          dev = send(source).first
          return unless dev

          return Shrink.new(dev) if source == :to_resize
          return Wipe.new(dev) if source == :to_wipe

          Delete.new(dev, related_partitions: false)
        end

        # @param deleted_sids [Array<Integer>] see {List}
        def done(deleted_sids)
          send(source_for_next).shift
          cleanup(to_delete_mandatory, deleted_sids)
          cleanup(to_delete_optional, deleted_sids)
          cleanup(to_resize, deleted_sids)
        end

        private

        # @return [ProposalSpaceSettings] proposal settings for making space
        attr_reader :settings

        # @return [Array<BlkDevice>] list of devices to be deleted (mandatory)
        attr_reader :to_delete_mandatory

        # @return [Array<BlkDevice>] list of devices to be deleted (optionally)
        attr_reader :to_delete_optional

        # @return [Array<Partition>] list of partitions to be shrunk
        attr_reader :to_resize

        # @return [Array<BlkDevice>] list of disks to be emptied if needed
        attr_reader :to_wipe

        # @see #add_optional_actions
        # @param disk [Disk]
        def add_wipe(disk)
          return if disk.partition_table?

          to_wipe << disk
        end

        # @see #add_optional_actions
        # @param disk [Disk]
        def add_resize(disk)
          return unless disk.partition_table?

          partitions = partitions(disk).select { |p| configured?(p, :resize) }
          return if partitions.empty?

          @to_resize = (to_resize + partitions).sort { |a, b| preferred_resize(a, b) }
        end

        # Compares two partitions to decide which one should be resized first
        #
        # @param part1 [Partition]
        # @param part2 [Partition]
        def preferred_resize(part1, part2)
          result = part2.recoverable_size <=> part1.recoverable_size
          return result unless result.zero?

          # Just to ensure stable sorting between different executions in case of draw
          part1.name <=> part2.name
        end

        # @see #add_optional_actions
        #
        # @param disk [Disk]
        def add_optional_delete(disk)
          return unless disk.partition_table?

          partitions = partitions(disk).select { |p| configured?(p, :delete) }
          to_delete_optional.concat(partitions.sort { |a, b| preferred_delete(a, b) })
        end

        # Compares two partitions to decide which one should be deleted first
        #
        # @param part1 [Partition]
        # @param part2 [Partition]
        def preferred_delete(part1, part2)
          # Mimic order from the auto strategy. We might consider other approaches in the future.
          part2.region.start <=> part1.region.start
        end

        # Whether the given action is configured for the given device at the proposal settings
        #
        # @see ProposalSpaceSettings#actions
        #
        # @param device [BlkDevice]
        # @param action [Symbol] :force_delete, :delete or :resize
        # @return [Boolean]
        def configured?(device, action)
          settings.actions[device.name]&.to_sym == action
        end

        # Removes devices with the given sids from a collection
        #
        # @param collection [Array<BlkDevice>]
        # @param deleted_sids [Array<Integer>]
        def cleanup(collection, deleted_sids)
          collection.delete_if { |d| deleted_sids.include?(d.sid) }
        end

        # Collection for the next action
        #
        # @return [Symbol]
        def source_for_next
          if to_delete_mandatory.any?
            :to_delete_mandatory
          elsif to_wipe.any?
            :to_wipe
          elsif to_resize.any?
            :to_resize
          else
            :to_delete_optional
          end
        end

        # Relevant partitions for the given disk
        def partitions(disk)
          disk.partitions.reject { |part| part.type.is?(:extended) }
        end
      end
    end
  end
end