Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package yast2-storage-ng for 
openSUSE:Factory checked in at 2026-03-08 17:26:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/yast2-storage-ng (Old)
 and      /work/SRC/openSUSE:Factory/.yast2-storage-ng.new.8177 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "yast2-storage-ng"

Sun Mar  8 17:26:23 2026 rev:182 rq:1337319 version:5.0.41

Changes:
--------
--- /work/SRC/openSUSE:Factory/yast2-storage-ng/yast2-storage-ng.changes        
2026-01-30 18:19:53.891531348 +0100
+++ 
/work/SRC/openSUSE:Factory/.yast2-storage-ng.new.8177/yast2-storage-ng.changes  
    2026-03-08 17:26:51.794752746 +0100
@@ -1,0 +2,9 @@
+Fri Mar  6 10:50:26 UTC 2026 - Ancor Gonzalez Sosa <[email protected]>
+
+- Added the needed infrastructure to specify what to do with the
+  existing volumes in a reused LVM volume group (related to
+  jsc#PED-15104, bsc#1254718 and gh#agama-project/agama#3171).
+- Fixed creation of thin pools within pre-existing thin pools.
+- 5.0.41
+
+-------------------------------------------------------------------

Old:
----
  yast2-storage-ng-5.0.40.tar.bz2

New:
----
  yast2-storage-ng-5.0.41.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ yast2-storage-ng.spec ++++++
--- /var/tmp/diff_new_pack.w808hG/_old  2026-03-08 17:26:52.402777719 +0100
+++ /var/tmp/diff_new_pack.w808hG/_new  2026-03-08 17:26:52.402777719 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           yast2-storage-ng
-Version:        5.0.40
+Version:        5.0.41
 Release:        0
 Summary:        YaST2 - Storage Configuration
 License:        GPL-2.0-only OR GPL-3.0-only

++++++ yast2-storage-ng-5.0.40.tar.bz2 -> yast2-storage-ng-5.0.41.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/package/yast2-storage-ng.changes 
new/yast2-storage-ng-5.0.41/package/yast2-storage-ng.changes
--- old/yast2-storage-ng-5.0.40/package/yast2-storage-ng.changes        
2026-01-29 10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/package/yast2-storage-ng.changes        
2026-03-06 12:42:19.000000000 +0100
@@ -1,4 +1,13 @@
 -------------------------------------------------------------------
+Fri Mar  6 10:50:26 UTC 2026 - Ancor Gonzalez Sosa <[email protected]>
+
+- Added the needed infrastructure to specify what to do with the
+  existing volumes in a reused LVM volume group (related to
+  jsc#PED-15104, bsc#1254718 and gh#agama-project/agama#3171).
+- Fixed creation of thin pools within pre-existing thin pools.
+- 5.0.41
+
+-------------------------------------------------------------------
 Thu Jan 29 09:00:40 UTC 2026 - Ancor Gonzalez Sosa <[email protected]>
 
 - Adjusted the criteria to check whether TPM-based full-disk
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/package/yast2-storage-ng.spec 
new/yast2-storage-ng-5.0.41/package/yast2-storage-ng.spec
--- old/yast2-storage-ng-5.0.40/package/yast2-storage-ng.spec   2026-01-29 
10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/package/yast2-storage-ng.spec   2026-03-06 
12:42:19.000000000 +0100
@@ -16,7 +16,7 @@
 #
 
 Name:           yast2-storage-ng
-Version:        5.0.40
+Version:        5.0.41
 Release:        0
 Summary:        YaST2 - Storage Configuration
 License:        GPL-2.0-only OR GPL-3.0-only
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/planned/lvm_vg.rb 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/planned/lvm_vg.rb
--- old/yast2-storage-ng-5.0.40/src/lib/y2storage/planned/lvm_vg.rb     
2026-01-29 10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/src/lib/y2storage/planned/lvm_vg.rb     
2026-03-06 12:42:19.000000000 +0100
@@ -135,6 +135,20 @@
         self.reuse_name = real_vg.vg_name
       end
 
+      # Redefines the corresponding method from the base class
+      #
+      # @see Device#assign_reuse
+      #
+      # For some reason (maybe just a historical mistake), the usage of 
#reuse_name is inconsistent
+      # in this class compared to the rest. Instead of using the device name, 
it uses the volume
+      # group name.
+      #
+      # @param device [Y2Storage::LvmVg]
+      def assign_reuse(device)
+        super(device)
+        @reuse_name = device.vg_name
+      end
+
       # Min size that a partition (or any other block device) must have to be 
useful as PV
       #
       # @return [DiskSize]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_creator.rb 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_creator.rb
--- old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_creator.rb       
2026-01-29 10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_creator.rb       
2026-03-06 12:42:19.000000000 +0100
@@ -20,6 +20,7 @@
 require "y2storage/planned"
 require "y2storage/disk_size"
 require "y2storage/proposal/creator_result"
+require "y2storage/proposal/lvm_space_maker"
 
 module Y2Storage
   module Proposal
@@ -39,8 +40,12 @@
       # Constructor
       #
       # @param original_devicegraph [Devicegraph] Initial devicegraph
-      def initialize(original_devicegraph)
+      # @param space_settings [ProposalSpaceSettings, nil] Optional settings 
to customize what to do
+      #   with every existing logical volume. If omitted, the traditional YaST 
approach is used
+      #   (ie. the strategy "auto" is used, see {LvmSpaceStrategies::Auto}).
+      def initialize(original_devicegraph, space_settings = nil)
         @original_devicegraph = original_devicegraph
+        @space_settings = space_settings
       end
 
       # Returns a copy of the original devicegraph in which the volume
@@ -129,61 +134,13 @@
 
       # Makes space for planned logical volumes
       #
-      # When making free space, three different policies can be followed:
-      #
-      # * :needed: remove logical volumes until there's enough space for
-      #            planned ones.
-      # * :remove: remove all logical volumes.
-      # * :keep:   keep all logical volumes.
-      #
       # This method modifies the volume group received as first argument.
       #
       # @param volume_group [LvmVg] volume group to clean-up
       # @param planned_vg   [Planned::LvmVg] planned logical volume
       def make_space(volume_group, planned_vg)
-        return if planned_vg.make_space_policy == :keep
-
-        case planned_vg.make_space_policy
-        when :needed
-          make_space_until_fit(volume_group, planned_vg.lvs)
-        when :remove
-          lvs_to_keep = planned_vg.all_lvs.select(&:reuse?).map(&:reuse_name)
-          remove_logical_volumes(volume_group, lvs_to_keep)
-        end
-      end
-
-      # Makes sure the given volume group has enough free extends to allocate
-      # all the planned volumes, by deleting the existing logical volumes.
-      #
-      # This method modifies the volume group received as first argument.
-      #
-      # FIXME: the current implementation does not guarantee than the freed
-      # space is the minimum valid one.
-      #
-      # @param volume_group [LvmVg] volume group to modify
-      def make_space_until_fit(volume_group, planned_lvs)
-        space_size = DiskSize.sum(planned_lvs.map(&:min_size))
-        missing = missing_vg_space(volume_group, space_size)
-        while missing > DiskSize.zero
-          lv_to_delete = delete_candidate(volume_group, missing)
-          if lv_to_delete.nil?
-            error_msg = "The volume group #{volume_group.vg_name} is not big 
enough"
-            raise NoDiskSpaceError, error_msg
-          end
-          volume_group.delete_lvm_lv(lv_to_delete)
-          missing = missing_vg_space(volume_group, space_size)
-        end
-      end
-
-      # Remove all logical volumes from a volume group
-      #
-      # This method modifies the volume group received as a first argument.
-      #
-      # @param volume_group [LvmVg]         volume group to remove logical 
volumes from
-      # @param lvs_to_keep  [Array<String>] name of logical volumes to keep
-      def remove_logical_volumes(volume_group, lvs_to_keep)
-        lvs_to_remove = volume_group.all_lvm_lvs.reject { |v| 
lvs_to_keep.include?(v.name) }
-        lvs_to_remove.each { |v| volume_group.delete_lvm_lv(v) }
+        space_maker = LvmSpaceMaker.new(volume_group, planned_vg, 
@space_settings)
+        space_maker.provide_space
       end
 
       # Creates a logical volume for each planned volume.
@@ -196,14 +153,17 @@
       # @return [Hash{String => Planned::LvmLv}] planned LVs indexed by the
       #   device name of the real LV devices that were created
       def create_logical_volumes(volume_group, planned_lvs)
-        adjusted_lvs = planned_lvs_in_vg(planned_lvs, volume_group)
+        adjusted_lvs = planned_lvs_in_vg(planned_lvs, 
volume_group).reject(&:reuse?)
         vg_size = volume_group.available_space
         lvs = Planned::LvmLv.distribute_space(adjusted_lvs, vg_size, rounding: 
volume_group.extent_size)
-        all_lvs = lvs + lvs.map(&:thin_lvs).flatten
+        all_lvs = lvs + lvs.map(&:thin_lvs).flatten + 
thin_lvs_from_reused_pools(planned_lvs)
         all_lvs.reject(&:reuse?).each_with_object({}) do |planned_lv, 
devices_map|
           new_lv = create_logical_volume(volume_group, planned_lv)
           devices_map[new_lv.name] = planned_lv
         end
+      rescue RuntimeError => e
+        log.info "The logical volumes do not fit into the volume group: #{e}"
+        raise NoDiskSpaceError
       end
 
       # Creates a logical volume in a volume group
@@ -229,32 +189,6 @@
         new_lv
       end
 
-      # Best logical volume to delete next while trying to make space for the
-      # planned volumes. It returns the smallest logical volume that would
-      # fulfill the goal. If no LV is big enough, it returns the biggest one.
-      def delete_candidate(volume_group, target_space)
-        lvs = volume_group.lvm_lvs
-        big_lvs = lvs.select { |lv| lv.size >= target_space }
-        if big_lvs.empty?
-          lvs.max_by(&:size)
-        else
-          big_lvs.min_by(&:size)
-        end
-      end
-
-      # Missing space in the volume group to fullfil a target
-      #
-      # @param volume_group [LvmVg]    Volume group
-      # @param target_space [DiskSize] Required space
-      def missing_vg_space(volume_group, target_space)
-        available = volume_group.available_space
-        if available > target_space
-          DiskSize.zero
-        else
-          target_space - available
-        end
-      end
-
       # Returns the name that is available taking original_name as a base. If
       # the name is already taken, the returned name will have a number
       # appended.
@@ -310,6 +244,15 @@
         end
       end
 
+      # Returns a list of planned logical thin volumes that should be created 
in thin pools
+      # that already exist.
+      #
+      # @param lvs [Array<Planned::LvmLv>] List of planned logical volumes
+      # @return [Array<Planned::LvmLv]
+      def thin_lvs_from_reused_pools(lvs)
+        lvs.select(&:reuse?).flat_map(&:thin_lvs)
+      end
+
       # Helper method to set stripes attributes
       #
       # @param lv         [LvmLv] Logical volume
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_maker.rb 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_maker.rb
--- old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_maker.rb   
1970-01-01 01:00:00.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_maker.rb   
2026-03-06 12:42:19.000000000 +0100
@@ -0,0 +1,77 @@
+# Copyright (c) [2026] 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/lvm_space_strategies"
+
+module Y2Storage
+  module Proposal
+    # Class to provide free space in a volume group by resizing and deleting 
pre-existing logical
+    # volumes. Analogous to what SpaceMaker does with partitions.
+    class LvmSpaceMaker
+      include Yast::Logger
+
+      # Strategies to use to find space. Equivalent to the corresponding 
SpaceMaker strategies.
+      STRATEGIES = {
+        auto:          LvmSpaceStrategies::Auto,
+        bigger_resize: LvmSpaceStrategies::BiggerResize
+      }
+      private_constant :STRATEGIES
+
+      # Constructor
+      #
+      # @param volume_group [LvmVg] volume group to clean-up
+      # @param planned_vg   [Planned::LvmVg] planned logical volume
+      # @param space_settings [ProposalSpaceSettings, nil] Optional settings. 
See
+      #   {LvmCreator#initialize}.
+      def initialize(volume_group, planned_vg, space_settings)
+        @volume_group = volume_group
+        @planned_vg = planned_vg
+        @space_settings = space_settings
+
+        if STRATEGIES[strategy]
+          @strategy_class = STRATEGIES[strategy]
+        else
+          err_msg = "Unsupported LVM strategy to make space: #{strategy}"
+          log.error err_msg
+          raise ArgumentError, err_msg
+        end
+      end
+
+      # Makes space for planned logical volumes
+      #
+      # This method modifies the volume group received as first argument.
+      def provide_space
+        log.info "Making space at LVM volume group with strategy #{strategy}"
+        log.info "vg: #{@volume_group.name}, planned: #{@planned_vg.inspect}"
+        @strategy_class.new(@volume_group, @planned_vg, 
@space_settings).provide_space
+      end
+
+      private
+
+      # Id of the strategy to be used
+      #
+      # @return [Symbol]
+      def strategy
+        return :auto unless @space_settings
+
+        @space_settings.strategy
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies/auto.rb
 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies/auto.rb
--- 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies/auto.rb
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies/auto.rb
 2026-03-06 12:42:19.000000000 +0100
@@ -0,0 +1,84 @@
+# Copyright (c) [2017-2026] 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/lvm_space_strategies/base"
+
+module Y2Storage
+  module Proposal
+    module LvmSpaceStrategies
+      # Traditional strategy used by the YaST and AutoYaST proposals to make 
space in a existing LVM
+      # volume group
+      #
+      # The behavior depends on the value of 
{Planned::LvmVg#make_space_policy}.
+      class Auto < Base
+        # Makes space for planned logical volumes
+        #
+        # When making free space, three different policies can be followed:
+        #
+        # * :needed: remove logical volumes until there's enough space for
+        #            planned ones.
+        # * :remove: remove all logical volumes.
+        # * :keep:   keep all logical volumes.
+        #
+        # @see Base#provide_space
+        def provide_space
+          return if planned_vg.make_space_policy == :keep
+
+          case planned_vg.make_space_policy
+          when :needed
+            make_space_until_fit
+          when :remove
+            lvs_to_keep = planned_vg.all_lvs.select(&:reuse?).map(&:reuse_name)
+            remove_logical_volumes(lvs_to_keep)
+          end
+        end
+
+        private
+
+        # Makes sure the given volume group has enough free extends to allocate
+        # all the planned volumes, by deleting the existing logical volumes.
+        #
+        # This method modifies the volume group received as first argument.
+        #
+        # FIXME: the current implementation does not guarantee that the freed
+        # space is the minimum valid one.
+        def make_space_until_fit
+          while missing_vg_space > DiskSize.zero
+            lv_to_delete = delete_candidate(volume_group.lvm_lvs)
+            if lv_to_delete.nil?
+              error_msg = "The volume group #{volume_group.vg_name} is not big 
enough"
+              raise NoDiskSpaceError, error_msg
+            end
+            volume_group.delete_lvm_lv(lv_to_delete)
+          end
+        end
+
+        # Remove all logical volumes from a volume group
+        #
+        # This method modifies the volume group received as a first argument.
+        #
+        # @param lvs_to_keep  [Array<String>] name of logical volumes to keep
+        def remove_logical_volumes(lvs_to_keep)
+          lvs_to_remove = volume_group.all_lvm_lvs.reject { |v| 
lvs_to_keep.include?(v.name) }
+          lvs_to_remove.each { |v| volume_group.delete_lvm_lv(v) }
+        end
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies/base.rb
 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies/base.rb
--- 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies/base.rb
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies/base.rb
 2026-03-06 12:42:19.000000000 +0100
@@ -0,0 +1,105 @@
+# Copyright (c) [2017-2026] 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/disk_size"
+
+module Y2Storage
+  module Proposal
+    module LvmSpaceStrategies
+      # Base class for the LVM space strategies
+      class Base
+        include Yast::Logger
+
+        # Constructor
+        #
+        # @param volume_group [LvmVg] volume group to clean-up
+        # @param planned_vg   [Planned::LvmVg] planned logical volume
+        # @param space_settings [ProposalSpaceSettings, nil] Optional 
settings. See
+        #   {LvmCreator#initialize}.
+        def initialize(volume_group, planned_vg, space_settings)
+          @volume_group = volume_group
+          @planned_vg = planned_vg
+          @space_settings = space_settings
+        end
+
+        # Makes space for planned logical volumes
+        #
+        # This method modifies the volume group assigned to the strategy 
object.
+        #
+        def provide_space
+          raise NotImplementedError
+        end
+
+        private
+
+        # @return [LvmVg] volume group to clean-up
+        attr_reader :volume_group
+
+        # @return [Planned::LvmVg] planned logical volume
+        attr_reader :planned_vg
+
+        # @return [ProposalSpaceSettings] settings to make space
+        attr_reader :space_settings
+
+        # Space that needs to be available in order to be able to create the 
volumes
+        #
+        # @return [DiskSize]
+        def target_space
+          @target_space ||= DiskSize.sum(
+            relevant_planned_lvs.map(&:min_size),
+            rounding: volume_group.extent_size
+          )
+        end
+
+        # Planned logical volumes to take into account when calculating the 
target space
+        #
+        # @see #target_space
+        #
+        # @return [Array<Planned::LvmLv>]
+        def relevant_planned_lvs
+          planned_vg.lvs.reject(&:reuse?).reject { |v| v.lv_type.is?(:thin) }
+        end
+
+        # Missing space in the volume group to fullfil the target
+        #
+        # @return [DiskSize]
+        def missing_vg_space
+          available = volume_group.available_space
+          if available > target_space
+            DiskSize.zero
+          else
+            target_space - available
+          end
+        end
+
+        # Best logical volume to delete next while trying to make space for the
+        # planned volumes. It returns the smallest logical volume that would
+        # fulfill the goal. If no LV is big enough, it returns the biggest one.
+        def delete_candidate(lvs)
+          big_lvs = lvs.select { |lv| lv.size >= missing_vg_space }
+          if big_lvs.empty?
+            lvs.max_by(&:size)
+          else
+            big_lvs.min_by(&:size)
+          end
+        end
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies/bigger_resize.rb
 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies/bigger_resize.rb
--- 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies/bigger_resize.rb
        1970-01-01 01:00:00.000000000 +0100
+++ 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies/bigger_resize.rb
        2026-03-06 12:42:19.000000000 +0100
@@ -0,0 +1,195 @@
+# Copyright (c) [2026] 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/lvm_space_strategies/base"
+
+module Y2Storage
+  module Proposal
+    module LvmSpaceStrategies
+      # Strategy used by the Agama proposal to make space in a existing LVM 
volume group
+      #
+      # This used the same rationale and general rules than the corresponding 
SpaceMaker strategy.
+      class BiggerResize < Base
+        # Makes space for planned logical volumes
+        #
+        # @see Base#provide_space
+        def provide_space
+          delete_mandatory
+          shrink_mandatory
+          return if done?
+
+          shrink_optional
+          return if done?
+
+          delete_optional
+          return if done?
+
+          error_msg = "The volume group #{volume_group.vg_name} is not big 
enough"
+          raise NoDiskSpaceError, error_msg
+        end
+
+        private
+
+        # Whether the goal has been achieved
+        #
+        # @return [Boolean]
+        def done?
+          missing_vg_space <= DiskSize.zero
+        end
+
+        # Executes the mandatory delete actions
+        def delete_mandatory
+          each_action(:delete) do |action, lv|
+            next unless action.mandatory
+
+            volume_group.delete_lvm_lv(lv)
+          end
+        end
+
+        # Executes the mandatory shrink actions
+        def shrink_mandatory
+          each_action(:resize) do |action, lv|
+            next unless action.max_size
+            next if lv.size <= action.max_size
+
+            lv.resize(action.max_size)
+          end
+        end
+
+        # Shrinks logical volumes as needed to make enough space for the new 
volumes
+        def shrink_optional
+          candidates = calculate_optional_shrinks
+          until done? || candidates.empty?
+            shrink = candidates.pop
+            recover = [missing_vg_space, shrink[:recoverable]].min
+            shrink[:lv].resize(shrink[:lv].size - recover)
+          end
+        end
+
+        # Deletes logical volumes as needed to make enough space for the new 
volumes
+        def delete_optional
+          candidates = calculate_optional_delete_lvs
+          until done? || candidates.empty?
+            lv = delete_candidate(candidates)
+            candidates.delete(lv)
+            volume_group.delete_lvm_lv(lv)
+          end
+        end
+
+        # @see #shrink_optional
+        def calculate_optional_shrinks
+          shrinks = []
+          each_action(:resize) do |action, lv|
+            next if min_for(action) && min_for(action) > lv.size
+            next if max_for(action) && max_for(action) < lv.size
+            # Resizing a thin volume would not get us any closer to our goal
+            next if lv.lv_type.is?(:thin)
+
+            shrinks << {
+              lv:          lv,
+              recoverable: recoverable_size(lv, action)
+            }
+          end
+
+          shrinks.sort { |a, b| preferred_shrink(a, b) }
+        end
+
+        # Compares two shrinking operations to decide which one should be 
executed first
+        #
+        # @return [Integer] -1, 0, or 1 just like the <=> ruby operator
+        def preferred_shrink(shrink1, shrink2)
+          result = shrink1[:recoverable] <=> shrink2[:recoverable]
+          return result unless result.zero?
+
+          # Just to ensure stable sorting between different executions in case 
of draw
+          shrink1[:lv].name <=> shrink2[:lv].name
+        end
+
+        # Max space that can be recovered from the given volume, having into 
account the
+        # restrictions imposed by its Resize action
+        #
+        # @see #shrink_optional
+        #
+        # @return [DiskSize]
+        def recoverable_size(lv, resize)
+          min = min_for(resize)
+          recoverable = lv.recoverable_size.floor(volume_group.extent_size)
+          return recoverable if min.nil? || min > lv.size
+
+          [recoverable, lv.size - min].min
+        end
+
+        # @see #delete_optional
+        #
+        # @return [Array<LvmLv>]
+        def calculate_optional_delete_lvs
+          lvs = []
+          each_action(:delete) do |action, lv|
+            next if action.mandatory
+            # Deleting a thin volume does not recover any useful space
+            next if lv.lv_type.is?(:thin)
+
+            lvs << lv
+          end
+          lvs
+        end
+
+        # Iterates over the actions of the given type
+        #
+        # @param type [:delete, :resize]
+        def each_action(type)
+          space_settings.public_send(:"#{type}_actions").each do |action|
+            lv = lv_for(action)
+            next unless lv
+
+            yield(action, lv)
+          end
+        end
+
+        # Logical volume associated to a given space action
+        #
+        # @param action [SpaceActions::Base]
+        # @return [LvmLv]
+        def lv_for(action)
+          volume_group.all_lvm_lvs.find { |v| v.name == action.device }
+        end
+
+        # Rounded min size for the resize action, if any
+        #
+        # Used to ensure all operations fit the extent size of the volume group
+        #
+        # @param action [SpaceActions::Resize]
+        # @return [DiskSize, nil]
+        def min_for(action)
+          action.min_size ? action.min_size.ceil(volume_group.extent_size) : 
nil
+        end
+
+        # Rounded max size for the resize action, if any
+        #
+        # Used to ensure all operations fit the extent size of the volume group
+        #
+        # @param action [SpaceActions::Resize]
+        # @return [DiskSize, nil]
+        def max_for(action)
+          action.max_size ? action.max_size.floor(volume_group.extent_size) : 
nil
+        end
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies.rb 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies.rb
--- 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/lvm_space_strategies.rb  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/lvm_space_strategies.rb  
    2026-03-06 12:42:19.000000000 +0100
@@ -0,0 +1,31 @@
+# Copyright (c) [2026] 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.
+
+module Y2Storage
+  module Proposal
+    # Namespace to group all the strategies used by LvmSpaceMaker.
+    #
+    # There is a strategy to mimic each strategy at SpaceMakerActions.
+    module LvmSpaceStrategy
+    end
+  end
+end
+
+require "y2storage/proposal/lvm_space_strategies/auto"
+require "y2storage/proposal/lvm_space_strategies/bigger_resize"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/space_maker_actions/bigger_resize_strategy.rb
 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/space_maker_actions/bigger_resize_strategy.rb
--- 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal/space_maker_actions/bigger_resize_strategy.rb
        2026-01-29 10:39:54.000000000 +0100
+++ 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal/space_maker_actions/bigger_resize_strategy.rb
        2026-03-06 12:42:19.000000000 +0100
@@ -251,12 +251,12 @@
 
         # All delete actions from the settings
         def delete_actions
-          settings.actions.select { |a| a.is?(:delete) }
+          settings.delete_actions
         end
 
         # All resize actions from the settings
         def resize_actions
-          settings.actions.select { |a| a.is?(:resize) }
+          settings.resize_actions
         end
       end
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal_space_settings.rb 
new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal_space_settings.rb
--- old/yast2-storage-ng-5.0.40/src/lib/y2storage/proposal_space_settings.rb    
2026-01-29 10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/src/lib/y2storage/proposal_space_settings.rb    
2026-03-06 12:42:19.000000000 +0100
@@ -128,5 +128,23 @@
     end
 
     alias_method :delete_forced?, :delete_forced
+
+    # All delete actions
+    #
+    # @see #actions
+    #
+    # @return [Array<SpaceActions::Delete>]
+    def delete_actions
+      actions.select { |a| a.is?(:delete) }
+    end
+
+    # All resize actions
+    #
+    # @see #actions
+    #
+    # @return [Array<SpaceActions::Resize>]
+    def resize_actions
+      actions.select { |a| a.is?(:resize) }
+    end
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/test/y2storage/autoinst_proposal_test.rb 
new/yast2-storage-ng-5.0.41/test/y2storage/autoinst_proposal_test.rb
--- old/yast2-storage-ng-5.0.40/test/y2storage/autoinst_proposal_test.rb        
2026-01-29 10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/test/y2storage/autoinst_proposal_test.rb        
2026-03-06 12:42:19.000000000 +0100
@@ -1228,6 +1228,33 @@
             expect(issue).to_not be_nil
           end
         end
+
+        context "creating a new thin volume in the existing pool" do
+          let(:pool_spec) do
+            { "lv_name" => "pool0", "size" => "200GiB", "pool" => true, 
"create" => false }
+          end
+
+          let(:home_spec) do
+            {
+              "mount" => "/home", "filesystem" => "ext4", "lv_name" => "home", 
"size" => "100GiB",
+              "used_pool" => "pool0"
+            }
+          end
+
+          let(:lvs) { [pool_spec, root_spec, home_spec] }
+
+          it "reuses the thin pool and create the new thin volumes" do
+            proposal.propose
+            devicegraph = proposal.devices
+            thin_vols = devicegraph.lvm_lvs.find { |v| v.lv_name == "pool0" 
}.lvm_lvs
+            expect(thin_vols.size).to eq 2
+            filesystems = thin_vols.map(&:filesystem)
+            expect(filesystems.map(&:mount_path)).to contain_exactly("/", 
"/home")
+            root_fs = filesystems.find { |f| f.mount_path == "/" }
+            # keep the same filesystem type
+            expect(root_fs.type).to eq(Y2Storage::Filesystems::Type::EXT4)
+          end
+        end
       end
     end
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/test/y2storage/proposal/lvm_creator_bigger_resize_test.rb
 
new/yast2-storage-ng-5.0.41/test/y2storage/proposal/lvm_creator_bigger_resize_test.rb
--- 
old/yast2-storage-ng-5.0.40/test/y2storage/proposal/lvm_creator_bigger_resize_test.rb
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/yast2-storage-ng-5.0.41/test/y2storage/proposal/lvm_creator_bigger_resize_test.rb
       2026-03-06 12:42:19.000000000 +0100
@@ -0,0 +1,245 @@
+#!/usr/bin/env rspec
+
+# Copyright (c) [2026] 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_relative "../spec_helper"
+require "y2storage"
+
+describe Y2Storage::Proposal::LvmCreator do
+  using Y2Storage::Refinements::SizeCasts
+
+  subject(:creator) { described_class.new(fake_devicegraph, space_settings) }
+
+  before { fake_scenario(scenario) }
+
+  let(:scenario) { "lvm-new-pvs" }
+  let(:space_settings) do
+    Y2Storage::ProposalSpaceSettings.new.tap do |settings|
+      settings.strategy = :bigger_resize
+      settings.actions = settings_actions
+    end
+  end
+  let(:settings_actions) { [] }
+  let(:delete) { Y2Storage::SpaceActions::Delete }
+  let(:resize) { Y2Storage::SpaceActions::Resize }
+
+  describe "#create_volumes" do
+    before { vg.reuse_name = reused_vg.vg_name }
+
+    let(:reused_vg) { fake_devicegraph.lvm_vgs.first }
+    let(:vg) { planned_vg(lvs: volumes) }
+    let(:pv_partitions) { [] }
+    let(:ext4) { Y2Storage::Filesystems::Type::EXT4 }
+
+    context "if there is enough space for the new LVs" do
+      let(:volumes) do
+        [
+          planned_lv(mount_point: "/1", type: :ext4, logical_volume_name: 
"one", min: 5.GiB),
+          planned_lv(mount_point: "/2", type: :ext4, logical_volume_name: 
"two", min: 5.GiB)
+        ]
+      end
+
+      context "and there are no mandatory actions" do
+        let(:settings_actions) do
+          [
+            delete.new("/dev/vg0/lv1", mandatory: false),
+            resize.new("/dev/vg0/lv1", min_size: 0.GiB)
+          ]
+        end
+
+        it "creates the new LVs" do
+          devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+          vg = devicegraph.lvm_vgs.first
+          expect(vg.lvm_lvs.map(&:lv_name)).to include "one", "two"
+        end
+
+        it "does not modify the pre-existing LVs" do
+          devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+          lvs = devicegraph.lvm_vgs.first.lvm_lvs
+          expect(lvs).to include(
+            an_object_having_attributes(lv_name: "lv1", size: 10.GiB),
+            an_object_having_attributes(lv_name: "lv2", size: 8.GiB)
+          )
+        end
+      end
+
+      context "and there are mandatory actions to delete logical volumes" do
+        let(:settings_actions) { [delete.new("/dev/vg0/lv1", mandatory: true)] 
}
+
+        it "deletes the designated pre-existing LVs and creates the new ones" 
do
+          devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+          lvs = devicegraph.lvm_vgs.first.lvm_lvs
+          expect(lvs.map(&:lv_name)).to contain_exactly "lv2", "one", "two"
+        end
+      end
+
+      context "and there are mandatory actions to resize logical volumes" do
+        let(:settings_actions) { [resize.new("/dev/vg0/lv1", max_size: 8.GiB)] 
}
+
+        let(:resize_info) do
+          instance_double("ResizeInfo", resize_ok?: true, min_size: 1.GiB, 
max_size: 30.GiB)
+        end
+
+        before do
+          allow_any_instance_of(Y2Storage::LvmLv)
+            .to receive(:detect_resize_info).and_return(resize_info)
+        end
+
+        it "creates the new LVs" do
+          devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+          vg = devicegraph.lvm_vgs.first
+          expect(vg.lvm_lvs.map(&:lv_name)).to include "one", "two"
+        end
+
+        it "resizes the pre-existing LVs according to the action" do
+          devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+          lvs = devicegraph.lvm_vgs.first.lvm_lvs
+          expect(lvs).to include(
+            an_object_having_attributes(lv_name: "lv1", size: 8.GiB),
+            an_object_having_attributes(lv_name: "lv2", size: 8.GiB)
+          )
+        end
+      end
+    end
+
+    context "if there is no enough space for the new LVs" do
+      let(:volumes) do
+        [
+          planned_lv(mount_point: "/1", type: :ext4, logical_volume_name: 
"one", min: 10.GiB),
+          planned_lv(mount_point: "/2", type: :ext4, logical_volume_name: 
"two", min: 5.GiB)
+        ]
+      end
+
+      context "and no actions are allowed" do
+        it "raises a NoDiskSpace exception" do
+          expect { creator.create_volumes(vg, pv_partitions) }
+            .to raise_error Y2Storage::NoDiskSpaceError
+        end
+      end
+
+      context "and delete and resizing are allowed" do
+        let(:settings_actions) do
+          [
+            delete.new("/dev/vg0/lv1", mandatory: false),
+            resize.new("/dev/vg0/lv1", min_size: 0.GiB)
+          ]
+        end
+
+        before do
+          allow_any_instance_of(Y2Storage::LvmLv)
+            .to receive(:detect_resize_info).and_return(resize_info)
+        end
+
+        context "and resizing is enough" do
+          let(:resize_info) do
+            instance_double("ResizeInfo", resize_ok?: true, min_size: 1.GiB, 
max_size: 30.GiB)
+          end
+
+          it "creates the new LVs" do
+            devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+            vg = devicegraph.lvm_vgs.first
+            expect(vg.lvm_lvs.map(&:lv_name)).to include "one", "two"
+          end
+
+          it "resizes the pre-existing LVs as needed" do
+            devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+            lvs = devicegraph.lvm_vgs.first.lvm_lvs
+            expect(lvs).to include(
+              an_object_having_attributes(lv_name: "lv1", size: 7.GiB - 4.MiB),
+              an_object_having_attributes(lv_name: "lv2", size: 8.GiB)
+            )
+          end
+        end
+
+        context "and resizing is not enough" do
+          let(:resize_info) do
+            instance_double("ResizeInfo", resize_ok?: true, min_size: 8.GiB, 
max_size: 30.GiB)
+          end
+
+          it "deletes the pre-existing LVs as needed to create the new ones" do
+            devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+            lvs = devicegraph.lvm_vgs.first.lvm_lvs
+            expect(lvs.map(&:lv_name)).to contain_exactly "lv2", "one", "two"
+          end
+        end
+      end
+    end
+
+    context "when creating new thin volumes in an existing pool" do
+      let(:scenario) { "lvm_with_nested_thin_lvs.xml" }
+      let(:reused_vg) { fake_devicegraph.find_by_name("/dev/vg_a") }
+      let(:reused_pool) { fake_devicegraph.find_by_name("/dev/vg_a/lvt_01") }
+
+      let(:volumes) { [planned_lv(logical_volume_name: reused_pool.lv_name)] }
+      let(:thin_volumes) do
+        [
+          planned_lv(
+            mount_point: "/1", type: :ext4, logical_volume_name: "one", min: 
100.GiB,
+            lv_type: Y2Storage::LvType::THIN
+          ),
+          planned_lv(
+            mount_point: "/2", type: :ext4, logical_volume_name: "two", min: 
100.GiB,
+            lv_type: Y2Storage::LvType::THIN
+          )
+        ]
+      end
+
+      let(:settings_actions) do
+        [
+          delete.new("/dev/vg_a/lv_01", mandatory: false),
+          resize.new("/dev/vg_a/lv_01", min_size: 0.GiB),
+          delete.new("/dev/vg_a/lv_02", mandatory: false),
+          resize.new("/dev/vg_a/lv_02", min_size: 0.GiB),
+          delete.new("/dev/vg_a/tv_01", mandatory: false),
+          resize.new("/dev/vg_a/tv_01", min_size: 0.GiB)
+        ]
+      end
+
+      before do
+        volumes.first.assign_reuse(reused_pool)
+        thin_volumes.each { |v| volumes.first.add_thin_lv(v) }
+        allow_any_instance_of(Y2Storage::LvmLv)
+          .to receive(:detect_resize_info).and_return(resize_info)
+      end
+
+      let(:resize_info) do
+        instance_double("ResizeInfo", resize_ok?: true, min_size: 1.GiB, 
max_size: 30.GiB)
+      end
+
+      it "does not delete or resize any other logical volume" do
+        initial_lvs = reused_vg.lvm_lvs
+        initial_thin_lvs = reused_pool.lvm_lvs
+
+        devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph
+        vg = devicegraph.find_by_name("/dev/vg_a")
+        pool = devicegraph.find_by_name("/dev/vg_a/lvt_01")
+
+        expect(vg.lvm_lvs.size).to eq initial_lvs.size
+        expect(Y2Storage::DiskSize.sum(vg.lvm_lvs.map(&:size)))
+          .to eq Y2Storage::DiskSize.sum(initial_lvs.map(&:size))
+
+        expect(pool.lvm_lvs.size).to eq initial_thin_lvs.size + 2
+        # The LvmCreator uses the total size of the thin pool as maximum size 
for new thin vols
+        expect(Y2Storage::DiskSize.sum(pool.lvm_lvs.map(&:size)))
+          .to eq Y2Storage::DiskSize.sum(initial_thin_lvs.map(&:size)) + 20.GiB
+      end
+    end
+  end
+end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/yast2-storage-ng-5.0.40/test/y2storage/proposal/lvm_creator_test.rb 
new/yast2-storage-ng-5.0.41/test/y2storage/proposal/lvm_creator_test.rb
--- old/yast2-storage-ng-5.0.40/test/y2storage/proposal/lvm_creator_test.rb     
2026-01-29 10:39:54.000000000 +0100
+++ new/yast2-storage-ng-5.0.41/test/y2storage/proposal/lvm_creator_test.rb     
2026-03-06 12:42:19.000000000 +0100
@@ -52,6 +52,20 @@
 
     let(:vg) { planned_vg(volume_group_name: "system", lvs: volumes) }
 
+    context "if a non-valid strategy is configured at the proposal space 
settings" do
+      subject(:creator) { described_class.new(fake_devicegraph, 
space_settings) }
+
+      let(:space_settings) do
+        Y2Storage::ProposalSpaceSettings.new.tap do |settings|
+          settings.strategy = :invented
+        end
+      end
+
+      it "raises an exception" do
+        expect { creator.create_volumes(vg, pv_partitions) }.to 
raise_exception(ArgumentError)
+      end
+    end
+
     context "if no volume group is reused" do
       it "creates a new volume group" do
         devicegraph = creator.create_volumes(vg, pv_partitions).devicegraph

Reply via email to